From 2e9ca0597d2e96f6ef2dc2d574f2d13eb2d8189c Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Mon, 4 Mar 2024 04:33:20 +0100 Subject: [PATCH 01/62] Replace some `gt-` classes with `tw-` (#29570) Replace 18 `gt-` prefixes with `tw-` with perl replacement. I manually checked them all with `rg` afterwards. (cherry picked from commit a2e90014ec20a1085449a66061389cfe0d12260f) Conflicts: templates/repo/header.tmpl because some of the header moved to header_fork.tmpl --- templates/admin/notice.tmpl | 2 +- templates/org/header.tmpl | 2 +- templates/package/view.tmpl | 2 +- templates/projects/list.tmpl | 2 +- templates/projects/new.tmpl | 2 +- templates/projects/view.tmpl | 4 ++-- templates/repo/branch/list.tmpl | 2 +- templates/repo/commit_page.tmpl | 2 +- templates/repo/commits_list_small.tmpl | 2 +- templates/repo/commits_table.tmpl | 2 +- templates/repo/diff/box.tmpl | 4 ++-- templates/repo/diff/comment_form.tmpl | 2 +- templates/repo/diff/compare.tmpl | 2 +- templates/repo/empty.tmpl | 2 +- templates/repo/header_fork.tmpl | 2 +- templates/repo/home.tmpl | 4 ++-- templates/repo/issue/list.tmpl | 2 +- templates/repo/issue/milestone_new.tmpl | 2 +- templates/repo/issue/view_content.tmpl | 4 ++-- .../repo/issue/view_content/comments.tmpl | 2 +- .../view_content/comments_delete_time.tmpl | 2 +- templates/repo/migrate/migrate.tmpl | 4 ++-- templates/repo/release/new.tmpl | 2 +- templates/repo/settings/branches.tmpl | 2 +- templates/repo/settings/githooks.tmpl | 2 +- templates/repo/settings/lfs_pointers.tmpl | 2 +- templates/repo/settings/options.tmpl | 2 +- templates/repo/settings/tags.tmpl | 2 +- templates/repo/unicode_escape_prompt.tmpl | 4 ++-- templates/repo/view_file.tmpl | 4 ++-- templates/repo/wiki/view.tmpl | 6 +++--- templates/status/500.tmpl | 2 +- templates/user/auth/captcha.tmpl | 2 +- templates/user/dashboard/issues.tmpl | 2 +- .../user/notification/notification_div.tmpl | 2 +- templates/user/settings/applications.tmpl | 4 ++-- templates/user/settings/repos.tmpl | 2 +- web_src/css/helpers.css | 20 ------------------- .../js/components/RepoBranchTagSelector.vue | 2 +- web_src/js/features/common-global.js | 2 +- web_src/js/modules/fomantic.js | 2 +- 41 files changed, 50 insertions(+), 70 deletions(-) diff --git a/templates/admin/notice.tmpl b/templates/admin/notice.tmpl index ed410425b5..e0abe4f8c0 100644 --- a/templates/admin/notice.tmpl +++ b/templates/admin/notice.tmpl @@ -31,7 +31,7 @@ <tr> <th></th> <th colspan="5"> - <form class="gt-float-right" method="post" action="{{AppSubUrl}}/admin/notices/empty"> + <form class="tw-float-right" method="post" action="{{AppSubUrl}}/admin/notices/empty"> {{.CsrfTokenHtml}} <button type="submit" class="ui red small button">{{ctx.Locale.Tr "admin.notices.delete_all"}}</button> </form> diff --git a/templates/org/header.tmpl b/templates/org/header.tmpl index efbbc43b1d..943557b1ca 100644 --- a/templates/org/header.tmpl +++ b/templates/org/header.tmpl @@ -7,7 +7,7 @@ {{if .Org.Visibility.IsLimited}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.limited_shortname"}}</span>{{end}} {{if .Org.Visibility.IsPrivate}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.private_shortname"}}</span>{{end}} </span> - <span class="gt-df gt-ac gt-gap-2 gt-ml-auto gt-font-16 gt-whitespace-nowrap"> + <span class="gt-df gt-ac gt-gap-2 gt-ml-auto gt-font-16 tw-whitespace-nowrap"> {{if .EnableFeed}} <a class="ui basic label button gt-mr-0" href="{{.Org.HomeLink}}.rss" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}"> {{svg "octicon-rss" 24}} diff --git a/templates/package/view.tmpl b/templates/package/view.tmpl index 0fa23d67fd..54af71126f 100644 --- a/templates/package/view.tmpl +++ b/templates/package/view.tmpl @@ -87,7 +87,7 @@ {{end}} <div class="divider"></div> <strong>{{ctx.Locale.Tr "packages.versions"}} ({{.TotalVersionCount}})</strong> - <a class="gt-float-right" href="{{$.PackageDescriptor.PackageWebLink}}/versions">{{ctx.Locale.Tr "packages.versions.view_all"}}</a> + <a class="tw-float-right" href="{{$.PackageDescriptor.PackageWebLink}}/versions">{{ctx.Locale.Tr "packages.versions.view_all"}}</a> <div class="ui relaxed list"> {{range .LatestVersions}} <div class="item gt-df"> diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl index 30fbd498a4..54a41221bf 100644 --- a/templates/projects/list.tmpl +++ b/templates/projects/list.tmpl @@ -10,7 +10,7 @@ {{ctx.Locale.PrettyNumber .ClosedCount}} {{ctx.Locale.Tr "repo.issues.closed_title"}} </a> </div> - <div class="gt-text-right"> + <div class="tw-text-right"> <a class="ui small primary button" href="{{$.Link}}/new">{{ctx.Locale.Tr "repo.projects.new"}}</a> </div> </div> diff --git a/templates/projects/new.tmpl b/templates/projects/new.tmpl index 711dbe842a..92ee36c1c4 100644 --- a/templates/projects/new.tmpl +++ b/templates/projects/new.tmpl @@ -55,7 +55,7 @@ </div> </div> <div class="divider"></div> - <div class="gt-text-right"> + <div class="tw-text-right"> <a class="ui cancel button" href="{{$.CancelLink}}"> {{ctx.Locale.Tr "repo.milestones.cancel"}} </a> diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index 3792ccca0e..1d03477a9f 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -165,9 +165,9 @@ <div class="divider"></div> - <div class="ui cards {{if and $canWriteProject (ne .ID 0)}}{{/* ID 0 is default column which cannot be moved */}}gt-cursor-grab{{end}}" data-url="{{$.Link}}/{{.ID}}" data-project="{{$.Project.ID}}" data-board="{{.ID}}" id="board_{{.ID}}"> + <div class="ui cards {{if and $canWriteProject (ne .ID 0)}}{{/* ID 0 is default column which cannot be moved */}}tw-cursor-grab{{end}}" data-url="{{$.Link}}/{{.ID}}" data-project="{{$.Project.ID}}" data-board="{{.ID}}" id="board_{{.ID}}"> {{range (index $.IssuesMap .ID)}} - <div class="issue-card gt-word-break {{if $canWriteProject}}gt-cursor-grab{{end}}" data-issue="{{.ID}}"> + <div class="issue-card gt-word-break {{if $canWriteProject}}tw-cursor-grab{{end}}" data-issue="{{.ID}}"> {{template "repo/issue/card" (dict "Issue" . "Page" $)}} </div> {{end}} diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl index 4aa0e22b78..8f796a8b8f 100644 --- a/templates/repo/branch/list.tmpl +++ b/templates/repo/branch/list.tmpl @@ -72,7 +72,7 @@ <div class="gt-df gt-ac"> {{ctx.Locale.Tr "repo.branches"}} </div> - <div class="gt-whitespace-nowrap"> + <div class="tw-whitespace-nowrap"> <form class="ignore-dirty" method="get"> <div class="ui tiny search input"> <input name="q" placeholder="{{ctx.Locale.Tr "repo.branch.search"}}" value="{{.Keyword}}" autofocus> diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index 7892a57163..1d900cdefb 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -184,7 +184,7 @@ </div> </div> {{if .Commit.Signature}} - <div class="ui bottom attached message gt-text-left gt-df gt-ac gt-sb commit-header-row gt-fw gt-mb-0 {{$class}}"> + <div class="ui bottom attached message tw-text-left gt-df gt-ac gt-sb commit-header-row gt-fw gt-mb-0 {{$class}}"> <div class="gt-df gt-ac"> {{if .Verification.Verified}} {{if ne .Verification.SigningUser.ID 0}} diff --git a/templates/repo/commits_list_small.tmpl b/templates/repo/commits_list_small.tmpl index 79e1bd6309..86e6b7225e 100644 --- a/templates/repo/commits_list_small.tmpl +++ b/templates/repo/commits_list_small.tmpl @@ -13,7 +13,7 @@ {{$commitLink:= printf "%s/commit/%s" $.comment.Issue.PullRequest.BaseRepo.Link (PathEscape .ID.String)}} - <span class="shabox gt-df gt-ac gt-float-right"> + <span class="shabox gt-df gt-ac tw-float-right"> {{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}} {{$class := "ui sha label"}} {{if .Signature}} diff --git a/templates/repo/commits_table.tmpl b/templates/repo/commits_table.tmpl index 054a3f6bec..70f673e27e 100644 --- a/templates/repo/commits_table.tmpl +++ b/templates/repo/commits_table.tmpl @@ -8,7 +8,7 @@ {{ctx.Locale.Tr "repo.commits.no_commits" $.BaseBranch $.HeadBranch}} {{end}} </div> - <div class="commits-table-right gt-whitespace-nowrap"> + <div class="commits-table-right tw-whitespace-nowrap"> {{if .PageIsCommits}} <form class="ignore-dirty" action="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/search"> <div class="ui tiny search input"> diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index f9f18208e5..4cfe9034c8 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -25,7 +25,7 @@ </div> <div class="diff-detail-actions"> {{if and .PageIsPullFiles $.SignedUserID (not .IsArchived) (not .DiffNotAvailable)}} - <div class="not-mobile gt-df gt-ac gt-fc gt-whitespace-nowrap gt-mr-2"> + <div class="not-mobile gt-df gt-ac gt-fc tw-whitespace-nowrap gt-mr-2"> <label for="viewed-files-summary" id="viewed-files-summary-label" data-text-changed-template="{{ctx.Locale.Tr "repo.pulls.viewed_files_label"}}"> {{ctx.Locale.Tr "repo.pulls.viewed_files_label" .Diff.NumViewedFiles .Diff.NumFiles}} </label> @@ -205,7 +205,7 @@ </div> {{if $showFileViewToggle}} {{/* for image or CSV, it can have a horizontal scroll bar, there won't be review comment context menu (position absolute) which would be clipped by "overflow" */}} - <div id="diff-rendered-{{$file.NameHash}}" class="file-body file-code {{if $.IsSplitStyle}}code-diff-split{{else}}code-diff-unified{{end}} gt-overflow-x-scroll"> + <div id="diff-rendered-{{$file.NameHash}}" class="file-body file-code {{if $.IsSplitStyle}}code-diff-split{{else}}code-diff-unified{{end}} tw-overflow-x-scroll"> <table class="chroma tw-w-full"> {{if $isImage}} {{template "repo/diff/image_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead "sniffedTypeBase" $sniffedTypeBase "sniffedTypeHead" $sniffedTypeHead}} diff --git a/templates/repo/diff/comment_form.tmpl b/templates/repo/diff/comment_form.tmpl index 54817d4740..6005ea28ef 100644 --- a/templates/repo/diff/comment_form.tmpl +++ b/templates/repo/diff/comment_form.tmpl @@ -27,7 +27,7 @@ <div class="field footer gt-mx-3"> <span class="markup-info">{{svg "octicon-markup"}} {{ctx.Locale.Tr "repo.diff.comment.markdown_info"}}</span> - <div class="gt-text-right"> + <div class="tw-text-right"> {{if $.reply}} <button class="ui submit primary tiny button btn-reply" type="submit">{{ctx.Locale.Tr "repo.diff.comment.reply"}}</button> <input type="hidden" name="reply" value="{{$.reply}}"> diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl index 17e0d3e712..cd56ebfcb2 100644 --- a/templates/repo/diff/compare.tmpl +++ b/templates/repo/diff/compare.tmpl @@ -208,7 +208,7 @@ <button class="ui button primary show-form">{{ctx.Locale.Tr "repo.pulls.new"}}</button> </div> {{else if .Repository.IsArchived}} - <div class="ui warning message gt-text-center"> + <div class="ui warning message tw-text-center"> {{if .Repository.ArchivedUnix.IsZero}} {{ctx.Locale.Tr "repo.archive.title"}} {{else}} diff --git a/templates/repo/empty.tmpl b/templates/repo/empty.tmpl index 7a0e6926c0..a3adc5d68c 100644 --- a/templates/repo/empty.tmpl +++ b/templates/repo/empty.tmpl @@ -6,7 +6,7 @@ <div class="sixteen wide column content"> {{template "base/alert" .}} {{if .Repository.IsArchived}} - <div class="ui warning message gt-text-center"> + <div class="ui warning message tw-text-center"> {{if .Repository.ArchivedUnix.IsZero}} {{ctx.Locale.Tr "repo.archive.title"}} {{else}} diff --git a/templates/repo/header_fork.tmpl b/templates/repo/header_fork.tmpl index e248a77850..3ac8889615 100644 --- a/templates/repo/header_fork.tmpl +++ b/templates/repo/header_fork.tmpl @@ -30,7 +30,7 @@ <div class="header"> {{ctx.Locale.Tr "repo.already_forked" .Name}} </div> - <div class="content gt-text-left"> + <div class="content tw-text-left"> <div class="ui list"> {{range $.UserAndOrgForks}} <div class="ui item gt-py-3"> diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index f9a4599b92..6f0a996841 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -39,7 +39,7 @@ <input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}"> {{range .Topics}} {{/* keey the same layout as Fomantic UI generated labels */}} - <a class="ui label transition visible gt-cursor-default gt-dib" data-value="{{.Name}}">{{.Name}}{{svg "octicon-x" 16 "delete icon"}}</a> + <a class="ui label transition visible tw-cursor-default gt-dib" data-value="{{.Name}}">{{.Name}}{{svg "octicon-x" 16 "delete icon"}}</a> {{end}} <div class="text"></div> </div> @@ -59,7 +59,7 @@ {{end}} {{if .Repository.IsArchived}} - <div class="ui warning message gt-text-center"> + <div class="ui warning message tw-text-center"> {{if .Repository.ArchivedUnix.IsZero}} {{ctx.Locale.Tr "repo.archive.title"}} {{else}} diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index 012b613fbf..62c1d00f00 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -6,7 +6,7 @@ {{if .PinnedIssues}} <div id="issue-pins" {{if .IsRepoAdmin}}data-is-repo-admin{{end}}> {{range .PinnedIssues}} - <div class="issue-card gt-word-break {{if $.IsRepoAdmin}}gt-cursor-grab{{end}}" data-move-url="{{$.Link}}/move_pin" data-issue-id="{{.ID}}"> + <div class="issue-card gt-word-break {{if $.IsRepoAdmin}}tw-cursor-grab{{end}}" data-move-url="{{$.Link}}/move_pin" data-issue-id="{{.ID}}"> {{template "repo/issue/card" (dict "Issue" . "Page" $ "isPinnedIssueCard" true)}} </div> {{end}} diff --git a/templates/repo/issue/milestone_new.tmpl b/templates/repo/issue/milestone_new.tmpl index 3e79ee7ee9..7a56d73ac9 100644 --- a/templates/repo/issue/milestone_new.tmpl +++ b/templates/repo/issue/milestone_new.tmpl @@ -39,7 +39,7 @@ <textarea name="content">{{.content}}</textarea> </div> <div class="divider"></div> - <div class="gt-text-right"> + <div class="tw-text-right"> {{if .PageIsEditMilestone}} <a class="ui primary basic button" href="{{.RepoLink}}/milestones"> {{ctx.Locale.Tr "repo.milestones.cancel"}} diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index f06f9137cd..5ce54df058 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -114,7 +114,7 @@ </div> </div> {{else if .Repository.IsArchived}} - <div class="ui warning message gt-text-center"> + <div class="ui warning message tw-text-center"> {{if .Issue.IsPull}} {{ctx.Locale.Tr "repo.archive.pull.nocomment"}} {{else}} @@ -124,7 +124,7 @@ {{end}} {{else}} {{/* not .IsSigned */}} {{if .Repository.IsArchived}} - <div class="ui warning message gt-text-center"> + <div class="ui warning message tw-text-center"> {{if .Issue.IsPull}} {{ctx.Locale.Tr "repo.archive.pull.nocomment"}} {{else}} diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 45cc65478f..ccd771256d 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -566,7 +566,7 @@ {{end}} </span> {{if and .IsForcePush $.Issue.PullRequest.BaseRepo.Name}} - <span class="gt-float-right comparebox"> + <span class="tw-float-right comparebox"> <a href="{{$.Issue.PullRequest.BaseRepo.Link}}/compare/{{PathEscape .OldCommit}}..{{PathEscape .NewCommit}}" rel="nofollow" class="ui compare label">{{ctx.Locale.Tr "repo.issues.force_push_compare"}}</a> </span> {{end}} diff --git a/templates/repo/issue/view_content/comments_delete_time.tmpl b/templates/repo/issue/view_content/comments_delete_time.tmpl index 95121b0dc7..2377e7c4f0 100644 --- a/templates/repo/issue/view_content/comments_delete_time.tmpl +++ b/templates/repo/issue/view_content/comments_delete_time.tmpl @@ -1,7 +1,7 @@ {{if and .comment.Time (.ctxData.Repository.IsTimetrackerEnabled ctx)}} {{/* compatibility with time comments made before v1.14 */}} {{if (not .comment.Time.Deleted)}} {{if (or .ctxData.IsAdmin (and .ctxData.IsSigned (eq .ctxData.SignedUserID .comment.PosterID)))}} - <span class="gt-float-right"> + <span class="tw-float-right"> <div class="ui mini modal issue-delete-time-modal" data-id="{{.comment.Time.ID}}"> <form method="post" class="delete-time-form" action="{{.ctxData.RepoLink}}/issues/{{.ctxData.Issue.Index}}/times/{{.comment.TimeID}}/delete"> {{.ctxData.CsrfTokenHtml}} diff --git a/templates/repo/migrate/migrate.tmpl b/templates/repo/migrate/migrate.tmpl index d1abb15374..8ba567ee6b 100644 --- a/templates/repo/migrate/migrate.tmpl +++ b/templates/repo/migrate/migrate.tmpl @@ -16,10 +16,10 @@ {{svg (printf "gitea-%s" .Name) 184}} {{end}} <div class="content"> - <div class="header gt-text-center"> + <div class="header tw-text-center"> {{.Title}} </div> - <div class="description gt-text-center"> + <div class="description tw-text-center"> {{ctx.Locale.Tr (printf "repo.migrate.%s.description" .Name)}} </div> </div> diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl index 46b1c9b291..30e783167c 100644 --- a/templates/repo/release/new.tmpl +++ b/templates/repo/release/new.tmpl @@ -64,7 +64,7 @@ <div class="flex-text-inline gt-f1"> <input name="attachment-edit-{{.UUID}}" class="attachment_edit" required value="{{.Name}}"> <input name="attachment-del-{{.UUID}}" type="hidden" value="false"> - <span class="ui text grey gt-whitespace-nowrap">{{.Size | FileSize}}</span> + <span class="ui text grey tw-whitespace-nowrap">{{.Size | FileSize}}</span> <span data-tooltip-content="{{ctx.Locale.Tr "repo.release.download_count" (ctx.Locale.PrettyNumber .DownloadCount)}}"> {{svg "octicon-info"}} </span> diff --git a/templates/repo/settings/branches.tmpl b/templates/repo/settings/branches.tmpl index fbdc12defb..f8896b504e 100644 --- a/templates/repo/settings/branches.tmpl +++ b/templates/repo/settings/branches.tmpl @@ -1,7 +1,7 @@ {{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings edit")}} <div class="repo-setting-content"> {{if .Repository.IsArchived}} - <div class="ui warning message gt-text-center"> + <div class="ui warning message tw-text-center"> {{ctx.Locale.Tr "repo.settings.archive.branchsettings_unavailable"}} </div> {{else}} diff --git a/templates/repo/settings/githooks.tmpl b/templates/repo/settings/githooks.tmpl index 03dd639671..294e545944 100644 --- a/templates/repo/settings/githooks.tmpl +++ b/templates/repo/settings/githooks.tmpl @@ -12,7 +12,7 @@ <div class="item truncated-item-container"> <span class="text {{if .IsActive}}green{{else}}grey{{end}} gt-mr-3">{{svg "octicon-dot-fill" 22}}</span> <span class="text truncate gt-f1 gt-mr-3">{{.Name}}</span> - <a class="muted gt-float-right gt-p-3" href="{{$.RepoLink}}/settings/hooks/git/{{.Name|PathEscape}}"> + <a class="muted tw-float-right gt-p-3" href="{{$.RepoLink}}/settings/hooks/git/{{.Name|PathEscape}}"> {{svg "octicon-pencil"}} </a> </div> diff --git a/templates/repo/settings/lfs_pointers.tmpl b/templates/repo/settings/lfs_pointers.tmpl index fdc6b536c2..fa2e376ff3 100644 --- a/templates/repo/settings/lfs_pointers.tmpl +++ b/templates/repo/settings/lfs_pointers.tmpl @@ -44,7 +44,7 @@ <td>{{if .InRepo}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td> <td>{{if .Exists}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td> <td>{{if .Accessible}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td> - <td class="gt-text-right"> + <td class="tw-text-right"> <a class="ui primary button" href="{{$.LFSFilesLink}}/find?oid={{.Oid}}&size={{.Size}}&sha={{.SHA}}">{{ctx.Locale.Tr "repo.settings.lfs_findcommits"}}</a> </td> </tr> diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 2c4a924ba7..31345ca4ca 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -80,7 +80,7 @@ </h4> <div class="ui attached segment"> {{if .Repository.IsArchived}} - <div class="ui warning message gt-text-center"> + <div class="ui warning message tw-text-center"> {{ctx.Locale.Tr "repo.settings.archive.mirrors_unavailable"}} </div> {{else}} diff --git a/templates/repo/settings/tags.tmpl b/templates/repo/settings/tags.tmpl index e4fcf2ee6b..31fb59e5e3 100644 --- a/templates/repo/settings/tags.tmpl +++ b/templates/repo/settings/tags.tmpl @@ -1,7 +1,7 @@ {{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings edit")}} <div class="repo-setting-content"> {{if .Repository.IsArchived}} - <div class="ui warning message gt-text-center"> + <div class="ui warning message tw-text-center"> {{ctx.Locale.Tr "repo.settings.archive.tagsettings_unavailable"}} </div> {{else}} diff --git a/templates/repo/unicode_escape_prompt.tmpl b/templates/repo/unicode_escape_prompt.tmpl index 9fe458f97b..ecc9c33d83 100644 --- a/templates/repo/unicode_escape_prompt.tmpl +++ b/templates/repo/unicode_escape_prompt.tmpl @@ -1,6 +1,6 @@ {{if .EscapeStatus}} {{if .EscapeStatus.HasInvisible}} - <div class="ui warning message unicode-escape-prompt gt-text-left"> + <div class="ui warning message unicode-escape-prompt tw-text-left"> <button class="btn close icon hide-panel" data-panel-closest=".message">{{svg "octicon-x" 16 "close inside"}}</button> <div class="header"> {{ctx.Locale.Tr "repo.invisible_runes_header"}} @@ -11,7 +11,7 @@ {{end}} </div> {{else if .EscapeStatus.HasAmbiguous}} - <div class="ui warning message unicode-escape-prompt gt-text-left"> + <div class="ui warning message unicode-escape-prompt tw-text-left"> <button class="btn close icon hide-panel" data-panel-closest=".message">{{svg "octicon-x" 16 "close inside"}}</button> <div class="header"> {{ctx.Locale.Tr "repo.ambiguous_runes_header"}} diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl index 91b10f744a..f8665b5d2c 100644 --- a/templates/repo/view_file.tmpl +++ b/templates/repo/view_file.tmpl @@ -1,12 +1,12 @@ <div {{if .ReadmeInList}}id="readme" {{end}}class="{{TabSizeClass .Editorconfig .FileName}} non-diff-file-content"> {{- if .FileError}} <div class="ui error message"> - <div class="text left gt-whitespace-pre">{{.FileError}}</div> + <div class="text left tw-whitespace-pre">{{.FileError}}</div> </div> {{end}} {{- if .FileWarning}} <div class="ui warning message"> - <div class="text left gt-whitespace-pre">{{.FileWarning}}</div> + <div class="text left tw-whitespace-pre">{{.FileWarning}}</div> </div> {{end}} diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl index 9fa05b5b5c..541b1e9b42 100644 --- a/templates/repo/wiki/view.tmpl +++ b/templates/repo/wiki/view.tmpl @@ -79,19 +79,19 @@ {{if .sidebarPresent}} <div class="markup wiki-content-sidebar"> {{if and .CanWriteWiki (not .Repository.IsMirror)}} - <a class="gt-float-right muted" href="{{.RepoLink}}/wiki/_Sidebar?action=_edit" aria-label="{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}">{{svg "octicon-pencil"}}</a> + <a class="tw-float-right muted" href="{{.RepoLink}}/wiki/_Sidebar?action=_edit" aria-label="{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}">{{svg "octicon-pencil"}}</a> {{end}} {{template "repo/unicode_escape_prompt" dict "EscapeStatus" .sidebarEscapeStatus "root" $}} {{.sidebarContent | SafeHTML}} </div> {{end}} - <div class="gt-clear-both"></div> + <div class="tw-clear-both"></div> {{if .footerPresent}} <div class="markup wiki-content-footer"> {{if and .CanWriteWiki (not .Repository.IsMirror)}} - <a class="gt-float-right muted" href="{{.RepoLink}}/wiki/_Footer?action=_edit" aria-label="{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}">{{svg "octicon-pencil"}}</a> + <a class="tw-float-right muted" href="{{.RepoLink}}/wiki/_Footer?action=_edit" aria-label="{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}">{{svg "octicon-pencil"}}</a> {{end}} {{template "repo/unicode_escape_prompt" dict "footerEscapeStatus" .sidebarEscapeStatus "root" $}} {{.footerContent | SafeHTML}} diff --git a/templates/status/500.tmpl b/templates/status/500.tmpl index c04b4af805..d88078b4b2 100644 --- a/templates/status/500.tmpl +++ b/templates/status/500.tmpl @@ -41,7 +41,7 @@ <div class="ui container gt-my-5"> {{if .ErrorMsg}} <p>{{ctx.Locale.Tr "error.occurred"}}:</p> - <pre class="gt-whitespace-pre-wrap gt-break-all">{{.ErrorMsg}}</pre> + <pre class="tw-whitespace-pre-wrap gt-break-all">{{.ErrorMsg}}</pre> {{end}} <div class="center gt-mt-5"> {{if or .SignedUser.IsAdmin .ShowFooterVersion}}<p>{{ctx.Locale.Tr "admin.config.app_ver"}}: {{AppVer}}</p>{{end}} diff --git a/templates/user/auth/captcha.tmpl b/templates/user/auth/captcha.tmpl index 1c3379e629..d4d1a82418 100644 --- a/templates/user/auth/captcha.tmpl +++ b/templates/user/auth/captcha.tmpl @@ -24,7 +24,7 @@ <div id="captcha" data-captcha-type="m-captcha" data-sitekey="{{.McaptchaSitekey}}" data-instance-url="{{.McaptchaURL}}"></div> </div> {{else if eq .CaptchaType "cfturnstile"}} - <div class="inline field gt-text-center"> + <div class="inline field tw-text-center"> <div id="captcha" data-captcha-type="cf-turnstile" data-sitekey="{{.CfTurnstileSitekey}}"></div> </div> <script src='https://challenges.cloudflare.com/turnstile/v0/api.js'></script> diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index 82622366e7..fd5960c31e 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -57,7 +57,7 @@ </form> <!-- Sort --> <div class="list-header-sort ui small dropdown type jump item"> - <span class="text gt-whitespace-nowrap"> + <span class="text tw-whitespace-nowrap"> {{ctx.Locale.Tr "repo.issues.filter_sort"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} </span> diff --git a/templates/user/notification/notification_div.tmpl b/templates/user/notification/notification_div.tmpl index d8f8d462d3..431aca0975 100644 --- a/templates/user/notification/notification_div.tmpl +++ b/templates/user/notification/notification_div.tmpl @@ -36,7 +36,7 @@ {{else}} {{range $notification := .Notifications}} <div class="notifications-item gt-df gt-ac gt-fw gt-gap-3 gt-p-3" id="notification_{{.ID}}" data-status="{{.Status}}"> - <div class="notifications-icon gt-ml-3 gt-mr-2 gt-self-start gt-mt-2"> + <div class="notifications-icon gt-ml-3 gt-mr-2 tw-self-start gt-mt-2"> {{if .Issue}} {{template "shared/issueicon" .Issue}} {{else}} diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index 7ce9a4b70f..e43cf2ebbe 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -61,11 +61,11 @@ </div> <div class="field"> <label>{{ctx.Locale.Tr "settings.repo_and_org_access"}}</label> - <label class="gt-cursor-pointer"> + <label class="tw-cursor-pointer"> <input class="enable-system gt-mt-2 gt-mr-2" type="radio" name="scope" value="{{$.AccessTokenScopePublicOnly}}"> {{ctx.Locale.Tr "settings.permissions_public_only"}} </label> - <label class="gt-cursor-pointer"> + <label class="tw-cursor-pointer"> <input class="enable-system gt-mt-2 gt-mr-2" type="radio" name="scope" value="" checked> {{ctx.Locale.Tr "settings.permissions_access_all"}} </label> diff --git a/templates/user/settings/repos.tmpl b/templates/user/settings/repos.tmpl index 5aabec547a..eeb2b6cbdd 100644 --- a/templates/user/settings/repos.tmpl +++ b/templates/user/settings/repos.tmpl @@ -32,7 +32,7 @@ {{else}} <span class="icon gt-dib gt-pt-3">{{svg "octicon-file-directory-fill"}}</span> <span class="name gt-dib gt-pt-3">{{$.ContextUser.Name}}/{{$dir}}</span> - <div class="gt-float-right"> + <div class="tw-float-right"> {{if $.allowAdopt}} <button class="ui button primary show-modal gt-p-3" data-modal="#adopt-unadopted-modal-{{$dirI}}"><span class="icon">{{svg "octicon-plus"}}</span><span class="label">{{ctx.Locale.Tr "repo.adopt_preexisting_label"}}</span></button> <div class="ui g-modal-confirm modal" id="adopt-unadopted-modal-{{$dirI}}"> diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css index 3579c193b1..71f3a619b9 100644 --- a/web_src/css/helpers.css +++ b/web_src/css/helpers.css @@ -52,34 +52,14 @@ Gitea's private styles use `g-` prefix. /* below class names match Tailwind CSS */ .gt-break-all { word-break: break-all !important; } .gt-content-center { align-content: center !important; } -.gt-cursor-default { cursor: default !important; } -.gt-cursor-pointer { cursor: pointer !important; } -.gt-cursor-grab { cursor: grab !important; } .gt-invisible { visibility: hidden !important; } .gt-items-start { align-items: flex-start !important; } .gt-pointer-events-none { pointer-events: none !important; } .gt-relative { position: relative !important; } -.gt-whitespace-nowrap { white-space: nowrap !important; } -.gt-whitespace-pre { white-space: pre !important; } -.gt-whitespace-pre-wrap { white-space: pre-wrap !important; } .gt-object-contain { object-fit: contain !important; } -.gt-self-center { align-self: center !important; } -.gt-self-start { align-self: flex-start !important; } -.gt-self-end { align-self: flex-end !important; } .gt-no-underline { text-decoration-line: none !important; } .gt-normal-case { text-transform: none !important; } .gt-italic { font-style: italic !important; } -.gt-overflow-x-auto { overflow-x: auto !important; } -.gt-overflow-x-scroll { overflow-x: scroll !important; } -.gt-overflow-y-hidden { overflow-y: hidden !important; } - -.gt-float-left { float: left !important; } -.gt-float-right { float: right !important; } -.gt-clear-both { clear: both !important; } - -.gt-text-center { text-align: center !important; } -.gt-text-left { text-align: left !important; } -.gt-text-right { text-align: right !important; } .gt-font-light { font-weight: var(--font-weight-light) !important; } .gt-font-normal { font-weight: var(--font-weight-normal) !important; } diff --git a/web_src/js/components/RepoBranchTagSelector.vue b/web_src/js/components/RepoBranchTagSelector.vue index bc7d979d99..70ce9bd6a0 100644 --- a/web_src/js/components/RepoBranchTagSelector.vue +++ b/web_src/js/components/RepoBranchTagSelector.vue @@ -278,7 +278,7 @@ export default sfc; // activate IDE's Vue plugin <div class="ui label" v-if="item.name===repoDefaultBranch && mode === 'branches'"> {{ textDefaultBranchLabel }} </div> - <a v-show="enableFeed && mode === 'branches'" role="button" class="rss-icon gt-float-right" :href="rssURLPrefix + item.url" target="_blank" @click.stop> + <a v-show="enableFeed && mode === 'branches'" role="button" class="rss-icon tw-float-right" :href="rssURLPrefix + item.url" target="_blank" @click.stop> <!-- creating a lot of Vue component is pretty slow, so we use a static SVG here --> <svg width="14" height="14" class="svg octicon-rss"><use href="#svg-symbol-octicon-rss"/></svg> </a> diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index c53d43cbb2..211253ef9a 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -234,7 +234,7 @@ export function initDropzone(el) { // Create a "Copy Link" element, to conveniently copy the image // or file link as Markdown to the clipboard const copyLinkElement = document.createElement('div'); - copyLinkElement.className = 'gt-text-center'; + copyLinkElement.className = 'tw-text-center'; // The a element has a hardcoded cursor: pointer because the default is overridden by .dropzone copyLinkElement.innerHTML = `<a href="#" style="cursor: pointer;">${svg('octicon-copy', 14, 'copy link')} Copy link</a>`; copyLinkElement.addEventListener('click', async (e) => { diff --git a/web_src/js/modules/fomantic.js b/web_src/js/modules/fomantic.js index 0c7a7ae641..6aafdd5ddc 100644 --- a/web_src/js/modules/fomantic.js +++ b/web_src/js/modules/fomantic.js @@ -17,7 +17,7 @@ export function initGiteaFomantic() { // By default, use "exact match" for full text search $.fn.dropdown.settings.fullTextSearch = 'exact'; // Do not use "cursor: pointer" for dropdown labels - $.fn.dropdown.settings.className.label += ' gt-cursor-default'; + $.fn.dropdown.settings.className.label += ' tw-cursor-default'; // Always use Gitea's SVG icons $.fn.dropdown.settings.templates.label = function(_value, text, preserveHTML, className) { const escape = $.fn.dropdown.settings.templates.escape; From eadf1d4fa3a1b26103819739923661d01f4d14b5 Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Mon, 4 Mar 2024 06:55:17 +0100 Subject: [PATCH 02/62] Inline the `css-variables-parser` dependency (#29571) Get rid of the `postcss@7` dependency by inlining this simple function. (cherry picked from commit d769b664dedb5f63b73146b58b21c0a772c2630d) --- package-lock.json | 30 ------------------------------ package.json | 1 - tailwind.config.js | 34 ++++++++++++++++++++++++---------- 3 files changed, 24 insertions(+), 41 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d1d2850c1..1df2a8b941 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,6 @@ "chartjs-plugin-zoom": "2.0.1", "clippie": "4.0.7", "css-loader": "6.10.0", - "css-variables-parser": "1.0.1", "dayjs": "1.11.10", "dropzone": "6.0.0-beta.2", "easymde": "2.18.0", @@ -4032,35 +4031,6 @@ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/css-variables-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/css-variables-parser/-/css-variables-parser-1.0.1.tgz", - "integrity": "sha512-GWaqrwGtAWVr/yjjE17iyvbcy+W3voe0vko1/xLCwFeYd3kTLstzUdVH+g5TTXejrtlsb1FS4L9rP6PmeTa8wQ==", - "dependencies": { - "postcss": "^7.0.36" - } - }, - "node_modules/css-variables-parser/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" - }, - "node_modules/css-variables-parser/node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", diff --git a/package.json b/package.json index 1152bfef72..d5e8170228 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "chartjs-plugin-zoom": "2.0.1", "clippie": "4.0.7", "css-loader": "6.10.0", - "css-variables-parser": "1.0.1", "dayjs": "1.11.10", "dropzone": "6.0.0-beta.2", "easymde": "2.18.0", diff --git a/tailwind.config.js b/tailwind.config.js index 7f36822001..fb17980568 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,9 +1,28 @@ import {readFileSync} from 'node:fs'; import {env} from 'node:process'; -import {parse} from 'css-variables-parser'; +import {parse} from 'postcss'; const isProduction = env.NODE_ENV !== 'development'; +function extractRootVars(css) { + const root = parse(css); + const vars = new Set(); + root.walkRules((rule) => { + if (rule.selector !== ':root') return; + rule.each((decl) => { + if (decl.value && decl.prop.startsWith('--')) { + vars.add(decl.prop.substring(2)); + } + }); + }); + return Array.from(vars); +} + +const vars = extractRootVars([ + readFileSync(new URL('web_src/css/themes/theme-gitea-light.css', import.meta.url), 'utf8'), + readFileSync(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url), 'utf8'), +].join('\n')); + export default { prefix: 'tw-', important: true, // the frameworks are mixed together, so tailwind needs to override other framework's styles @@ -23,15 +42,10 @@ export default { theme: { colors: { // make `tw-bg-red` etc work with our CSS variables - ...Object.fromEntries( - Object.keys(parse([ - readFileSync(new URL('web_src/css/themes/theme-gitea-light.css', import.meta.url), 'utf8'), - readFileSync(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url), 'utf8'), - ].join('\n'), {})).filter((prop) => prop.startsWith('color-')).map((prop) => { - const color = prop.substring(6); - return [color, `var(--color-${color})`]; - }) - ), + ...Object.fromEntries(vars.filter((prop) => prop.startsWith('color-')).map((prop) => { + const color = prop.substring(6); + return [color, `var(--color-${color})`]; + })), inherit: 'inherit', current: 'currentcolor', transparent: 'transparent', From bb09ad2b63570c80418b4b9a10f7dbbb349448ab Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Mon, 4 Mar 2024 15:50:21 +0800 Subject: [PATCH 03/62] Allow options to disable user ssh keys configuration from the interface on app.ini (#29447) Follow #29275 Extract from #20549 Fix #24716 --------- Co-authored-by: delvh <dev.lh@web.de> (cherry picked from commit 8e12ba34bab7e728ac93ccfaecbe91e053ef1c89) Conflicts: custom/conf/app.example.ini trivial context conflict --- custom/conf/app.example.ini | 5 +++-- .../administration/config-cheat-sheet.en-us.md | 5 +++-- .../administration/config-cheat-sheet.zh-cn.md | 5 +++-- modules/setting/admin.go | 1 + routers/api/v1/user/key.go | 11 +++++++++++ routers/web/user/setting/keys.go | 16 ++++++++++++++++ templates/user/settings/keys.tmpl | 4 +++- 7 files changed, 40 insertions(+), 7 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 4d077643f5..b3896bc31c 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1496,10 +1496,11 @@ LEVEL = Info ;; ;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled ;DEFAULT_EMAIL_NOTIFICATIONS = enabled -;; Disabled features for users, could be "deletion","manage_gpg_keys" more features can be disabled in future +;; Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false ;SEND_NOTIFICATION_EMAIL_ON_NEW_USER = false -;; Disabled features for users, could be "deletion", more features can be disabled in future +;; Disabled features for users, could be "deletion", "manage_ssh_keys","manage_gpg_keys" more features can be disabled in future ;; - deletion: a user cannot delete their own account +;; - manage_ssh_keys: a user cannot configure ssh keys ;; - manage_gpg_keys: a user cannot configure gpg keys ;USER_DISABLED_FEATURES = diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index ea6e1eb1a4..8a01711949 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -518,9 +518,10 @@ And the following unique queues: - `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled - `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations. -- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion`, `manage_gpg_keys` and more features can be added in future. +- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys` and more features can be added in future. - `deletion`: User cannot delete their own account. - - `manage_gpg_keys`: User cannot configure gpg keys + - `manage_ssh_keys`: User cannot configure ssh keys. + - `manage_gpg_keys`: User cannot configure gpg keys. ## Security (`security`) diff --git a/docs/content/administration/config-cheat-sheet.zh-cn.md b/docs/content/administration/config-cheat-sheet.zh-cn.md index 5cc5734359..7b102eda8e 100644 --- a/docs/content/administration/config-cheat-sheet.zh-cn.md +++ b/docs/content/administration/config-cheat-sheet.zh-cn.md @@ -497,9 +497,10 @@ Gitea 创建以下非唯一队列: - `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**:用户电子邮件通知的默认配置(用户可配置)。选项:enabled、onmention、disabled - `DISABLE_REGULAR_ORG_CREATION`: **false**:禁止普通(非管理员)用户创建组织。 -- `USER_DISABLED_FEATURES`:**_empty_** 禁用的用户特性,当前允许为空或者 `deletion`,`manage_gpg_keys` 未来可以增加更多设置。 +- `USER_DISABLED_FEATURES`:**_empty_** 禁用的用户特性,当前允许为空或者 `deletion`,`manage_ssh_keys`, `manage_gpg_keys` 未来可以增加更多设置。 - `deletion`: 用户不能通过界面或者API删除他自己。 - - `manage_gpg_keys`: 用户不能配置 GPG 密钥 + - `manage_ssh_keys`: 用户不能通过界面或者API配置SSH Keys。 + - `manage_gpg_keys`: 用户不能配置 GPG 密钥。 ## 安全性 (`security`) diff --git a/modules/setting/admin.go b/modules/setting/admin.go index c292db9c8f..35ffa9efbf 100644 --- a/modules/setting/admin.go +++ b/modules/setting/admin.go @@ -22,5 +22,6 @@ func loadAdminFrom(rootCfg ConfigProvider) { const ( UserFeatureDeletion = "deletion" + UserFeatureManageSSHKeys = "manage_ssh_keys" UserFeatureManageGPGKeys = "manage_gpg_keys" ) diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go index ada6759f8e..bcbfd93bd3 100644 --- a/routers/api/v1/user/key.go +++ b/routers/api/v1/user/key.go @@ -5,6 +5,7 @@ package user import ( std_ctx "context" + "fmt" "net/http" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -198,6 +199,11 @@ func GetPublicKey(ctx *context.APIContext) { // CreateUserPublicKey creates new public key to given user by ID. func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) { + if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) + return + } + content, err := asymkey_model.CheckPublicKeyString(form.Key) if err != nil { repo.HandleCheckKeyStringError(ctx, err) @@ -263,6 +269,11 @@ func DeletePublicKey(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" + if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) + return + } + id := ctx.ParamsInt64(":id") externallyManaged, err := asymkey_model.PublicKeyIsExternallyManaged(ctx, id) if err != nil { diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index cb01913bda..d2b60fc809 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -159,6 +159,11 @@ func KeysPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.verify_gpg_key_success", keyID)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "ssh": + if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) + return + } + content, err := asymkey_model.CheckPublicKeyString(form.Content) if err != nil { if db.IsErrSSHDisabled(err) { @@ -198,6 +203,11 @@ func KeysPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "verify_ssh": + if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) + return + } + token := asymkey_model.VerificationToken(ctx.Doer, 1) lastToken := asymkey_model.VerificationToken(ctx.Doer, 0) @@ -240,6 +250,11 @@ func DeleteKey(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success")) } case "ssh": + if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) + return + } + keyID := ctx.FormInt64("id") external, err := asymkey_model.PublicKeyIsExternallyManaged(ctx, keyID) if err != nil { @@ -318,4 +333,5 @@ func loadKeysData(ctx *context.Context) { ctx.Data["VerifyingID"] = ctx.FormString("verify_gpg") ctx.Data["VerifyingFingerprint"] = ctx.FormString("verify_ssh") + ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures } diff --git a/templates/user/settings/keys.tmpl b/templates/user/settings/keys.tmpl index a44bf50048..e0f5e426ae 100644 --- a/templates/user/settings/keys.tmpl +++ b/templates/user/settings/keys.tmpl @@ -1,6 +1,8 @@ {{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings sshkeys")}} <div class="user-setting-content"> - {{template "user/settings/keys_ssh" .}} + {{if not ($.UserDisabledFeatures.Contains "manage_ssh_keys")}} + {{template "user/settings/keys_ssh" .}} + {{end}} {{template "user/settings/keys_principal" .}} {{if not ($.UserDisabledFeatures.Contains "manage_gpg_keys")}} {{template "user/settings/keys_gpg" .}} From 3c05986fcfd4f3ec489b25e406d1d4181fac45b8 Mon Sep 17 00:00:00 2001 From: wxiaoguang <wxiaoguang@gmail.com> Date: Mon, 4 Mar 2024 18:57:30 +0800 Subject: [PATCH 04/62] Fix incorrect package link method calls in templates (#29580) Fix #29562 Follow #29531 (cherry picked from commit 7ec4c65ea5d5a3765d24ee68ae421f2f911b6b5d) --- templates/admin/packages/list.tmpl | 2 +- templates/package/settings.tmpl | 2 +- templates/package/shared/cleanup_rules/preview.tmpl | 2 +- templates/package/shared/list.tmpl | 2 +- templates/package/shared/versionlist.tmpl | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/admin/packages/list.tmpl b/templates/admin/packages/list.tmpl index cf860dab2a..1f86803d55 100644 --- a/templates/admin/packages/list.tmpl +++ b/templates/admin/packages/list.tmpl @@ -63,7 +63,7 @@ </td> <td>{{.Package.Type.Name}}</td> <td class="gt-ellipsis gt-max-width-12rem">{{.Package.Name}}</td> - <td class="gt-ellipsis gt-max-width-12rem"><a href="{{.FullWebLink}}">{{.Version.Version}}</a></td> + <td class="gt-ellipsis gt-max-width-12rem"><a href="{{.VersionWebLink}}">{{.Version.Version}}</a></td> <td><a href="{{.Creator.HomeLink}}">{{.Creator.Name}}</a></td> <td> {{if .Repository}} diff --git a/templates/package/settings.tmpl b/templates/package/settings.tmpl index 10e26c7010..9424baf493 100644 --- a/templates/package/settings.tmpl +++ b/templates/package/settings.tmpl @@ -10,7 +10,7 @@ {{template "user/overview/header" .}} {{end}} {{template "base/alert" .}} - <p><a href="{{.PackageDescriptor.FullWebLink}}">{{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}})</a> / <strong>{{ctx.Locale.Tr "repo.settings"}}</strong></p> + <p><a href="{{.PackageDescriptor.VersionWebLink}}">{{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}})</a> / <strong>{{ctx.Locale.Tr "repo.settings"}}</strong></p> <h4 class="ui top attached header"> {{ctx.Locale.Tr "packages.settings.link"}} </h4> diff --git a/templates/package/shared/cleanup_rules/preview.tmpl b/templates/package/shared/cleanup_rules/preview.tmpl index 7a50d5ccca..cff8e8249f 100644 --- a/templates/package/shared/cleanup_rules/preview.tmpl +++ b/templates/package/shared/cleanup_rules/preview.tmpl @@ -19,7 +19,7 @@ <tr> <td>{{.Package.Type.Name}}</td> <td>{{.Package.Name}}</td> - <td><a href="{{.FullWebLink}}">{{.Version.Version}}</a></td> + <td><a href="{{.VersionWebLink}}">{{.Version.Version}}</a></td> <td><a href="{{.Creator.HomeLink}}">{{.Creator.Name}}</a></td> <td>{{FileSize .CalculateBlobSize}}</td> <td>{{DateTime "short" .Version.CreatedUnix}}</td> diff --git a/templates/package/shared/list.tmpl b/templates/package/shared/list.tmpl index 67c686675c..7b10e52ff7 100644 --- a/templates/package/shared/list.tmpl +++ b/templates/package/shared/list.tmpl @@ -20,7 +20,7 @@ <div class="flex-item"> <div class="flex-item-main"> <div class="flex-item-title"> - <a href="{{.FullWebLink}}">{{.Package.Name}}</a> + <a href="{{.VersionWebLink}}">{{.Package.Name}}</a> <span class="ui label">{{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}}</span> </div> <div class="flex-item-body"> diff --git a/templates/package/shared/versionlist.tmpl b/templates/package/shared/versionlist.tmpl index eee952c096..59d6d89b53 100644 --- a/templates/package/shared/versionlist.tmpl +++ b/templates/package/shared/versionlist.tmpl @@ -23,7 +23,7 @@ <div class="flex-list"> <div class="flex-item"> <div class="flex-item-main"> - <a class="flex-item-title" href="{{.FullWebLink}}">{{.Version.LowerVersion}}</a> + <a class="flex-item-title" href="{{.VersionWebLink}}">{{.Version.LowerVersion}}</a> <div class="flex-item-body"> {{ctx.Locale.Tr "packages.published_by" (TimeSinceUnix .Version.CreatedUnix ctx.Locale) .Creator.HomeLink .Creator.GetDisplayName}} </div> From a44805e5b3880ccf2d82808f3854dd829bf08c30 Mon Sep 17 00:00:00 2001 From: wxiaoguang <wxiaoguang@gmail.com> Date: Mon, 4 Mar 2024 20:02:45 +0800 Subject: [PATCH 05/62] Remove unnecessary SanitizeHTML from code (#29575) * "mail/issue/default.tmpl": the body is rendered by backend `markdown.RenderString() HTML`, it has been already sanitized * "repo/settings/webhook/base_list.tmpl": "Description" is prepared by backend `ctx.Tr`, it doesn't need to be sanitized (cherry picked from commit dae7f1ebdbe19620f40e110b285f7c0ecd0bb33b) --- docs/content/administration/mail-templates.en-us.md | 2 +- docs/content/administration/mail-templates.zh-cn.md | 2 +- modules/templates/helper.go | 10 ++-------- modules/templates/helper_test.go | 1 - templates/mail/issue/default.tmpl | 2 +- templates/repo/settings/webhook/base_list.tmpl | 2 +- templates/status/500.tmpl | 2 +- 7 files changed, 7 insertions(+), 14 deletions(-) diff --git a/docs/content/administration/mail-templates.en-us.md b/docs/content/administration/mail-templates.en-us.md index 0154fe55d0..4026b89975 100644 --- a/docs/content/administration/mail-templates.en-us.md +++ b/docs/content/administration/mail-templates.en-us.md @@ -224,7 +224,7 @@ Please check [Gitea's logs](administration/logging-config.md) for error messages {{if not (eq .Body "")}} <h3>Message content</h3> <hr> - {{.Body | SanitizeHTML}} + {{.Body}} {{end}} </p> <hr> diff --git a/docs/content/administration/mail-templates.zh-cn.md b/docs/content/administration/mail-templates.zh-cn.md index e8c2817336..3c7c2a9397 100644 --- a/docs/content/administration/mail-templates.zh-cn.md +++ b/docs/content/administration/mail-templates.zh-cn.md @@ -207,7 +207,7 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/ {{if not (eq .Body "")}} <h3>消息内容:</h3> <hr> - {{.Body | SanitizeHTML}} + {{.Body}} {{end}} </p> <hr> diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 9afcb96fc9..7ef051cc0b 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -211,14 +211,8 @@ func SafeHTML(s any) template.HTML { } // SanitizeHTML sanitizes the input by pre-defined markdown rules -func SanitizeHTML(s any) template.HTML { - switch v := s.(type) { - case string: - return template.HTML(markup.Sanitize(v)) - case template.HTML: - return template.HTML(markup.Sanitize(string(v))) - } - panic(fmt.Sprintf("unexpected type %T", s)) +func SanitizeHTML(s string) template.HTML { + return template.HTML(markup.Sanitize(s)) } func HTMLEscape(s any) template.HTML { diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go index 3365278ac2..64f29d033e 100644 --- a/modules/templates/helper_test.go +++ b/modules/templates/helper_test.go @@ -64,5 +64,4 @@ func TestHTMLFormat(t *testing.T) { func TestSanitizeHTML(t *testing.T) { assert.Equal(t, template.HTML(`<a href="/" rel="nofollow">link</a> xss <div>inline</div>`), SanitizeHTML(`<a href="/">link</a> <a href="javascript:">xss</a> <div style="dangerous">inline</div>`)) - assert.Equal(t, template.HTML(`<a href="/" rel="nofollow">link</a> xss <div>inline</div>`), SanitizeHTML(template.HTML(`<a href="/">link</a> <a href="javascript:">xss</a> <div style="dangerous">inline</div>`))) } diff --git a/templates/mail/issue/default.tmpl b/templates/mail/issue/default.tmpl index 796dc403b7..95a1555e87 100644 --- a/templates/mail/issue/default.tmpl +++ b/templates/mail/issue/default.tmpl @@ -58,7 +58,7 @@ {{.locale.Tr "mail.issue.action.new" .Doer.Name .Issue.Index}} {{end}} {{else}} - {{.Body | SanitizeHTML}} + {{.Body}} {{end -}} {{- range .ReviewComments}} <hr> diff --git a/templates/repo/settings/webhook/base_list.tmpl b/templates/repo/settings/webhook/base_list.tmpl index 00f9a48ba7..e56929b70f 100644 --- a/templates/repo/settings/webhook/base_list.tmpl +++ b/templates/repo/settings/webhook/base_list.tmpl @@ -10,7 +10,7 @@ <div class="ui attached segment"> <div class="ui list"> <div class="item"> - {{.Description | SanitizeHTML}} + {{.Description}} </div> {{range .Webhooks}} <div class="item truncated-item-container"> diff --git a/templates/status/500.tmpl b/templates/status/500.tmpl index d88078b4b2..fb159ea361 100644 --- a/templates/status/500.tmpl +++ b/templates/status/500.tmpl @@ -1,5 +1,5 @@ {{/* This page should only depend the minimal template functions/variables, to avoid triggering new panics. -* base template functions: AppName, AssetUrlPrefix, AssetVersion, AppSubUrl, ThemeName, SanitizeHTML +* base template functions: AppName, AssetUrlPrefix, AssetVersion, AppSubUrl, ThemeName * ctx.Locale * .Flash * .ErrorMsg From 145bebc829c03cbb078e518d7364d27bcf60d96c Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Mon, 4 Mar 2024 21:48:59 +0800 Subject: [PATCH 06/62] Make admin pages wider because of left sidebar added and some tables become too narrow (#29581) Fix #25939 screenshots <img width="1895" alt="image" src="https://github.com/go-gitea/gitea/assets/81045/937eb28d-bb7d-4765-b580-bc991d61f467"> (cherry picked from commit fad232054542ade88268304fea9b09f778d74a29) --- templates/admin/layout_head.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/admin/layout_head.tmpl b/templates/admin/layout_head.tmpl index 0067f336e0..b326c82a6c 100644 --- a/templates/admin/layout_head.tmpl +++ b/templates/admin/layout_head.tmpl @@ -3,7 +3,7 @@ <div class="ui container gt-mb-4"> {{template "base/alert" .ctxData}} </div> - <div class="ui container flex-container"> + <div class="ui container fluid padded flex-container"> {{template "admin/navbar" .ctxData}} <div class="flex-container-main"> {{/* block: admin-setting-content */}} From e7297d423f566a383c8861c4aaee028606591038 Mon Sep 17 00:00:00 2001 From: charles <30816317+charles7668@users.noreply.github.com> Date: Mon, 4 Mar 2024 22:41:53 +0800 Subject: [PATCH 07/62] Do not exceed display for the PR page buttons on smaller screens (#29418) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #29189. This is the result after the fix at a width of 768 pixels.  (cherry picked from commit c660149a7079c2b06d4ee6dce2a45804d6d4d7f6) --- web_src/css/repo.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 31cff0ca15..87ce829a78 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -942,7 +942,7 @@ margin-bottom: -0.25rem; } -@media (max-width: 767.98px) { +@media (max-width: 991.98px) { .repository.view.issue .comment-list .comment .merge-section .item-section { align-items: flex-start; flex-direction: column; From 2cb07e8415b3fbad1919252978ed27d0c0f235db Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Mon, 4 Mar 2024 21:24:12 +0100 Subject: [PATCH 08/62] Regenerate fomantic lockfile and build it with our browserslist (#29560) 1. Make fomantic build use [our browserslist](https://github.com/go-gitea/gitea/blob/e3524c63d6d42865ea8288af89b372544d35474b/package.json#L99). I found no other way than to sed-replace into it's js, the normal browserlist config files do not work. The effect of this change is the removal of some uneeded CSS vendor prefixes. 2. Regenerate `web_src/fomantic/package-lock.json`, this might shut up some security scanners. --------- Co-authored-by: Giteabot <teabot@gitea.io> (cherry picked from commit da3b7f5039158faae4b617ca878061f8a4f3e489) --- Makefile | 1 + web_src/fomantic/build/semantic.css | 109 -- web_src/fomantic/package-lock.json | 1846 ++++++++++++++++++--------- 3 files changed, 1267 insertions(+), 689 deletions(-) diff --git a/Makefile b/Makefile index bc3837f065..1a835e31fd 100644 --- a/Makefile +++ b/Makefile @@ -961,6 +961,7 @@ fomantic: cd $(FOMANTIC_WORK_DIR) && npm install --no-save cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/ + $(SED_INPLACE) -e 's/ overrideBrowserslist\r/ overrideBrowserslist: ["defaults"]\r/g' $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/tasks/config/tasks.js cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build # fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event $(SED_INPLACE) -e 's/clickEvent[ \t]*=/clickEvent = "click", unstableClickEvent =/g' $(FOMANTIC_WORK_DIR)/build/semantic.js diff --git a/web_src/fomantic/build/semantic.css b/web_src/fomantic/build/semantic.css index ad3f13bb58..476f7ebf11 100644 --- a/web_src/fomantic/build/semantic.css +++ b/web_src/fomantic/build/semantic.css @@ -156,7 +156,6 @@ width: 1.28571429em; height: 1.28571429em; border-radius: 500rem; - -webkit-animation: loader 0.6s infinite linear; animation: loader 0.6s infinite linear; border: 0.2em solid currentColor; color: #FFFFFF; @@ -530,7 +529,6 @@ border-top-left-radius: inherit; border-bottom-left-radius: inherit; text-align: center; - -webkit-animation: none; animation: none; padding: 0.78571429em 0 0.78571429em 0; margin: 0; @@ -594,7 +592,6 @@ /* Loading Icon in Labeled Button */ .ui.labeled.icon.button > .loading.icon:before { - -webkit-animation: loader 2s linear infinite; animation: loader 2s linear infinite; } @@ -2350,7 +2347,6 @@ .ui.checkbox { position: relative; display: inline-block; - -webkit-backface-visibility: hidden; backface-visibility: hidden; outline: none; vertical-align: baseline; @@ -3227,9 +3223,7 @@ background: rgba(0, 0, 0, 0.85); opacity: 0; line-height: 1; - -webkit-animation-fill-mode: both; animation-fill-mode: both; - -webkit-animation-duration: 0.5s; animation-duration: 0.5s; transition: background-color 0.5s linear; flex-direction: column; @@ -3458,44 +3452,25 @@ body.dimmable > .dimmer { } .ui[class*="center dimmer"].transition[class*="fade up"].in { - -webkit-animation-name: fadeInUpCenter; animation-name: fadeInUpCenter; } .ui[class*="center dimmer"].transition[class*="fade down"].in { - -webkit-animation-name: fadeInDownCenter; animation-name: fadeInDownCenter; } .ui[class*="center dimmer"].transition[class*="fade up"].out { - -webkit-animation-name: fadeOutUpCenter; animation-name: fadeOutUpCenter; } .ui[class*="center dimmer"].transition[class*="fade down"].out { - -webkit-animation-name: fadeOutDownCenter; animation-name: fadeOutDownCenter; } .ui[class*="center dimmer"].bounce.transition { - -webkit-animation-name: bounceCenter; animation-name: bounceCenter; } -@-webkit-keyframes fadeInUpCenter { - 0% { - opacity: 0; - transform: translateY(-40%); - -webkit-transform: translateY(calc(-40% - 0.5px)); - } - - 100% { - opacity: 1; - transform: translateY(-50%); - -webkit-transform: translateY(calc(-50% - 0.5px)); - } -} - @keyframes fadeInUpCenter { 0% { opacity: 0; @@ -3510,20 +3485,6 @@ body.dimmable > .dimmer { } } -@-webkit-keyframes fadeInDownCenter { - 0% { - opacity: 0; - transform: translateY(-60%); - -webkit-transform: translateY(calc(-60% - 0.5px)); - } - - 100% { - opacity: 1; - transform: translateY(-50%); - -webkit-transform: translateY(calc(-50% - 0.5px)); - } -} - @keyframes fadeInDownCenter { 0% { opacity: 0; @@ -3538,20 +3499,6 @@ body.dimmable > .dimmer { } } -@-webkit-keyframes fadeOutUpCenter { - 0% { - opacity: 1; - transform: translateY(-50%); - -webkit-transform: translateY(calc(-50% - 0.5px)); - } - - 100% { - opacity: 0; - transform: translateY(-45%); - -webkit-transform: translateY(calc(-45% - 0.5px)); - } -} - @keyframes fadeOutUpCenter { 0% { opacity: 1; @@ -3566,20 +3513,6 @@ body.dimmable > .dimmer { } } -@-webkit-keyframes fadeOutDownCenter { - 0% { - opacity: 1; - transform: translateY(-50%); - -webkit-transform: translateY(calc(-50% - 0.5px)); - } - - 100% { - opacity: 0; - transform: translateY(-55%); - -webkit-transform: translateY(calc(-55% - 0.5px)); - } -} - @keyframes fadeOutDownCenter { 0% { opacity: 1; @@ -3594,21 +3527,6 @@ body.dimmable > .dimmer { } } -@-webkit-keyframes bounceCenter { - 0%, 20%, 50%, 80%, 100% { - transform: translateY(-50%); - -webkit-transform: translateY(calc(-50% - 0.5px)); - } - - 40% { - transform: translateY(calc(-50% - 30px)); - } - - 60% { - transform: translateY(calc(-50% - 15px)); - } -} - @keyframes bounceCenter { 0%, 20%, 50%, 80%, 100% { transform: translateY(-50%); @@ -3672,7 +3590,6 @@ body.dimmable > .dimmer { display: none; outline: none; top: 100%; - min-width: -webkit-max-content; min-width: -moz-max-content; min-width: max-content; margin: 0; @@ -4068,7 +3985,6 @@ select.ui.dropdown { .ui.selection.dropdown .menu { overflow-x: hidden; overflow-y: auto; - -webkit-backface-visibility: hidden; backface-visibility: hidden; -webkit-overflow-scrolling: touch; border-top-width: 0 !important; @@ -4280,7 +4196,6 @@ select.ui.dropdown { @supports (-webkit-touch-callout: none) or (-webkit-overflow-scrolling: touch) or (-moz-appearance:none) { @media (-moz-touch-enabled), (pointer: coarse) { .ui.dropdown .scrollhint.menu:not(.hidden):before { - -webkit-animation: scrollhint 2s ease 2; animation: scrollhint 2s ease 2; content: ''; z-index: 15; @@ -4301,18 +4216,6 @@ select.ui.dropdown { border-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0)) 1 100%; } -@-webkit-keyframes scrollhint { - 0% { - opacity: 1; - top: 100%; - } - - 100% { - opacity: 0; - top: 0; - } -} - @keyframes scrollhint { 0% { opacity: 1; @@ -4414,7 +4317,6 @@ select.ui.dropdown { .ui.search.dropdown .menu { overflow-x: hidden; overflow-y: auto; - -webkit-backface-visibility: hidden; backface-visibility: hidden; -webkit-overflow-scrolling: touch; } @@ -4682,7 +4584,6 @@ select.ui.dropdown { margin: -0.64285714em 0 0 -0.64285714em; width: 1.28571429em; height: 1.28571429em; - -webkit-animation: loader 0.6s infinite linear; animation: loader 0.6s infinite linear; border: 0.2em solid #767676; border-radius: 500rem; @@ -5072,7 +4973,6 @@ select.ui.dropdown { .ui.scrolling.dropdown .menu { overflow-x: hidden; overflow-y: auto; - -webkit-backface-visibility: hidden; backface-visibility: hidden; -webkit-overflow-scrolling: touch; min-width: 100% !important; @@ -5564,7 +5464,6 @@ select.ui.dropdown { line-height: 1; height: 1em; width: 1.23em; - -webkit-backface-visibility: hidden; backface-visibility: hidden; font-weight: normal; font-style: normal; @@ -7052,7 +6951,6 @@ select.ui.dropdown { margin: -1.5em 0 0 -1.5em; width: 3em; height: 3em; - -webkit-animation: loader 0.6s infinite linear; animation: loader 0.6s infinite linear; border: 0.2em solid #767676; border-radius: 500rem; @@ -10299,7 +10197,6 @@ a.ui.black.header:hover { margin: -0.64285714em 0 0 -0.64285714em; width: 1.28571429em; height: 1.28571429em; - -webkit-animation: loader 0.6s infinite linear; animation: loader 0.6s infinite linear; border: 0.2em solid #767676; border-radius: 500rem; @@ -13306,7 +13203,6 @@ ol.ui.suffixed.list li:before, left: 100%; /* IE needs 0, all others support max-content to show dropdown icon inline, so keep both settings! */ min-width: 0; - min-width: -webkit-max-content; min-width: -moz-max-content; min-width: max-content; margin: 0 0 0 0; @@ -16722,7 +16618,6 @@ Floated Menu / Item margin: -0.64285714em 0 0 -0.64285714em; width: 1.28571429em; height: 1.28571429em; - -webkit-animation: loader 0.6s infinite linear; animation: loader 0.6s infinite linear; border: 0.2em solid #767676; border-radius: 500rem; @@ -16904,7 +16799,6 @@ Floated Menu / Item .ui.search.short > .results { overflow-x: hidden; overflow-y: auto; - -webkit-backface-visibility: hidden; backface-visibility: hidden; -webkit-overflow-scrolling: touch; } @@ -17201,7 +17095,6 @@ Floated Menu / Item justify-content: center; align-items: stretch; max-width: initial; - -webkit-animation: none; animation: none; overflow: visible; padding: 1em 1em; @@ -17494,7 +17387,6 @@ Floated Menu / Item margin: -1.5em 0 0 -1.5em; width: 3em; height: 3em; - -webkit-animation: loader 0.6s infinite linear; animation: loader 0.6s infinite linear; border: 0.2em solid #767676; border-radius: 500rem; @@ -17996,7 +17888,6 @@ input::selection { margin: -1.25em 0 0 -1.25em; width: 2.5em; height: 2.5em; - -webkit-animation: loader 0.6s infinite linear; animation: loader 0.6s infinite linear; border: 0.2em solid #767676; border-radius: 500rem; diff --git a/web_src/fomantic/package-lock.json b/web_src/fomantic/package-lock.json index 8283122eb3..4000d10da7 100644 --- a/web_src/fomantic/package-lock.json +++ b/web_src/fomantic/package-lock.json @@ -19,6 +19,100 @@ "findup": "bin/findup.js" } }, + "node_modules/@choojs/findup/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@octokit/auth-token": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", @@ -28,35 +122,97 @@ } }, "node_modules/@octokit/core": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", - "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.0.1.tgz", + "integrity": "sha512-MIpPQXu8Y8GjHwXM81JLveiV+DHJZtLMcB5nKekBGOl3iAtk0HT3i12Xl8Biybu+bCS1+k4qbuKEq5d0RxNRnQ==", "peer": true, "dependencies": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.3", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.0.0", + "@octokit/request": "^9.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^12.0.0", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/auth-token": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.0.1.tgz", + "integrity": "sha512-RTmWsLfig8SBoiSdgvCht4BXl1CHU89Co5xiQ5JF19my/sIRDFCQ1RPrmK0exgqUZuNm39C/bV8+/83+MJEjGg==", + "peer": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/endpoint": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.0.0.tgz", + "integrity": "sha512-emBcNDxBdC1y3+knJonS5zhUB/CG6TihubxM2U1/pG/Z1y3a4oV0Gzz3lmkCvWWQI6h3tqBAX9MgCBFp+M68Jw==", + "peer": true, + "dependencies": { + "@octokit/types": "^12.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "peer": true + }, + "node_modules/@octokit/core/node_modules/@octokit/request": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.0.1.tgz", + "integrity": "sha512-kL+cAcbSl3dctYLuJmLfx6Iku2MXXy0jszhaEIjQNaCp4zjHXrhVAHeuaRdNvJjW9qjl3u1MJ72+OuBP0YW/pg==", + "peer": true, + "dependencies": { + "@octokit/endpoint": "^10.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" } }, "node_modules/@octokit/core/node_modules/@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.0.2.tgz", + "integrity": "sha512-WtRVpoHcNXs84+s9s/wqfHaxM68NGMg8Av7h59B50OVO0PwwMx+2GgQ/OliUd0iQBSNWgR6N8afi/KjSHbXHWw==", "peer": true, "dependencies": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" + "@octokit/types": "^12.0.0" + }, + "engines": { + "node": ">= 18" } }, + "node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "peer": true, + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/core/node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "peer": true + }, "node_modules/@octokit/core/node_modules/universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", "peer": true }, "node_modules/@octokit/endpoint": { @@ -70,31 +226,89 @@ } }, "node_modules/@octokit/endpoint/node_modules/universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" }, "node_modules/@octokit/graphql": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", - "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.0.1.tgz", + "integrity": "sha512-lLDb6LhC1gBj2CxEDa5Xk10+H/boonhs+3Mi6jpRyetskDKNHe6crMeKmUE2efoLofMP8ruannLlCUgpTFmVzQ==", "peer": true, "dependencies": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", - "universal-user-agent": "^6.0.0" + "@octokit/request": "^9.0.0", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql/node_modules/@octokit/endpoint": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.0.0.tgz", + "integrity": "sha512-emBcNDxBdC1y3+knJonS5zhUB/CG6TihubxM2U1/pG/Z1y3a4oV0Gzz3lmkCvWWQI6h3tqBAX9MgCBFp+M68Jw==", + "peer": true, + "dependencies": { + "@octokit/types": "^12.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "peer": true + }, + "node_modules/@octokit/graphql/node_modules/@octokit/request": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.0.1.tgz", + "integrity": "sha512-kL+cAcbSl3dctYLuJmLfx6Iku2MXXy0jszhaEIjQNaCp4zjHXrhVAHeuaRdNvJjW9qjl3u1MJ72+OuBP0YW/pg==", + "peer": true, + "dependencies": { + "@octokit/endpoint": "^10.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql/node_modules/@octokit/request-error": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.0.2.tgz", + "integrity": "sha512-WtRVpoHcNXs84+s9s/wqfHaxM68NGMg8Av7h59B50OVO0PwwMx+2GgQ/OliUd0iQBSNWgR6N8afi/KjSHbXHWw==", + "peer": true, + "dependencies": { + "@octokit/types": "^12.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "peer": true, + "dependencies": { + "@octokit/openapi-types": "^20.0.0" } }, "node_modules/@octokit/graphql/node_modules/universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", "peer": true }, "node_modules/@octokit/openapi-types": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", - "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==" + "version": "12.11.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", + "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" }, "node_modules/@octokit/plugin-paginate-rest": { "version": "1.1.2", @@ -179,9 +393,9 @@ } }, "node_modules/@octokit/request/node_modules/universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" }, "node_modules/@octokit/rest": { "version": "16.43.2", @@ -207,11 +421,25 @@ } }, "node_modules/@octokit/types": { - "version": "6.34.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", - "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", + "version": "6.41.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", + "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", "dependencies": { - "@octokit/openapi-types": "^11.2.0" + "@octokit/openapi-types": "^12.11.0" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" } }, "node_modules/@types/expect": { @@ -220,23 +448,29 @@ "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==" }, "node_modules/@types/node": { - "version": "14.18.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.21.tgz", - "integrity": "sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==" + "version": "20.11.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz", + "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/vinyl": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.6.tgz", - "integrity": "sha512-ayJ0iOCDNHnKpKTgBG6Q6JOnHTj9zFta+3j2b8Ejza0e4cvRyMn0ZoLEmbPrTHe5YYRlDYPvPWVdV4cTaRyH7g==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.11.tgz", + "integrity": "sha512-vPXzCLmRp74e9LsP8oltnWKTH+jBwt86WgRUb4Pc9Lf3pkMVGyvIo2gm9bODeGfCay2DBB/hAWDuvf07JcK4rw==", "dependencies": { "@types/expect": "^1.20.4", "@types/node": "*" } }, "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/accord": { "version": "0.29.0", @@ -590,9 +824,15 @@ } }, "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", + "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] }, "node_modules/async-settle": { "version": "1.0.0", @@ -703,9 +943,9 @@ } }, "node_modules/before-after-hook": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", - "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" }, "node_modules/better-console": { "version": "1.0.1", @@ -735,6 +975,15 @@ "url": "https://bevry.me/fund" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -765,9 +1014,9 @@ } }, "node_modules/browserslist": { - "version": "4.20.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz", - "integrity": "sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "funding": [ { "type": "opencollective", @@ -776,14 +1025,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001349", - "electron-to-chromium": "^1.4.147", - "escalade": "^3.1.1", - "node-releases": "^2.0.5", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -792,22 +1044,20 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/browserslist/node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, "node_modules/btoa-lite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==" }, "node_modules/buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", + "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", "engines": { - "node": ">=0.4.0" + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/buffer-from": { @@ -835,12 +1085,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -855,9 +1111,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001352", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz", - "integrity": "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==", + "version": "1.0.30001591", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz", + "integrity": "sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==", "funding": [ { "type": "opencollective", @@ -866,6 +1122,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -948,61 +1208,16 @@ "node": ">=0.10.0" } }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/clean-css": { @@ -1171,14 +1386,20 @@ } }, "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } }, "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/concat-map": { "version": "0.0.1", @@ -1217,12 +1438,9 @@ } }, "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dependencies": { - "safe-buffer": "~5.1.1" - } + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/copy-anything": { "version": "2.0.6", @@ -1284,12 +1502,15 @@ } }, "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" } }, "node_modules/dateformat": { @@ -1317,9 +1538,9 @@ } }, "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "engines": { "node": ">=0.10" } @@ -1336,9 +1557,9 @@ } }, "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "engines": { "node": ">=0.10.0" } @@ -1362,11 +1583,28 @@ "node": ">= 0.10" } }, - "node_modules/define-properties": { + "node_modules/define-data-property": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" }, @@ -1494,24 +1732,73 @@ "node": ">=0.10.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/editorconfig": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", - "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", "dependencies": { - "commander": "^2.19.0", - "lru-cache": "^4.1.5", - "semver": "^5.6.0", - "sigmund": "^1.0.1" + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" }, "bin": { "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/editorconfig/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/electron-to-chromium": { - "version": "1.4.151", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.151.tgz", - "integrity": "sha512-XaG2LpZi9fdiWYOqJh0dJy4SlVywCvpgYXhzOlZTp4JqSKqxn5URqOjbm9OMYB3aInA2GuHQiem1QUOc1yT0Pw==" + "version": "1.4.690", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.690.tgz", + "integrity": "sha512-+2OAGjUx68xElQhydpcbqH50hE8Vs2K6TkAeLhICYfndb67CVH0UsZaijmRUE3rHlIxU1u0jxwhgVe6fK3YANA==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/end-of-stream": { "version": "1.4.4", @@ -1521,6 +1808,18 @@ "once": "^1.4.0" } }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -1529,14 +1828,34 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es5-ext": { - "version": "0.10.61", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", - "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "hasInstallScript": true, "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" }, "engines": { @@ -1554,12 +1873,15 @@ } }, "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" } }, "node_modules/es6-weak-map": { @@ -1574,9 +1896,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "engines": { "node": ">=6" } @@ -1589,6 +1911,29 @@ "node": ">=0.8.0" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/execa": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", @@ -1634,61 +1979,16 @@ "node": ">=0.10.0" } }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/expand-tilde": { @@ -1703,18 +2003,13 @@ } }, "node_modules/ext": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", - "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", "dependencies": { - "type": "^2.5.0" + "type": "^2.7.2" } }, - "node_modules/ext/node_modules/type": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", - "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==" - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1803,6 +2098,12 @@ "node": ">=4" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, "node_modules/fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -1968,6 +2269,86 @@ "node": ">=0.10.0" } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/foreground-child/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/fork-stream": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz", @@ -2010,10 +2391,31 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/get-caller-file": { "version": "1.0.3", @@ -2033,13 +2435,18 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2216,10 +2623,21 @@ "node": ">= 0.10" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/growly": { "version": "1.3.0", @@ -2889,21 +3307,32 @@ } }, "node_modules/gulp-json-editor": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/gulp-json-editor/-/gulp-json-editor-2.5.6.tgz", - "integrity": "sha512-66Xr6Q6m4mUNd0OOHflMB/RHgFNnLjlHgizOzUcx9CyMRymVZEM+/SpZcCDlvThBdXtQwXpdvtSepxVY/V6nQA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/gulp-json-editor/-/gulp-json-editor-2.6.0.tgz", + "integrity": "sha512-Ni0ZUpNrhesHiTlHQth/Nv1rXCn0LUicEvzA5XuGy186C4PVeNoRjfuAIQrbmt3scKv8dgGbCs0hd77ScTw7hA==", "dependencies": { - "deepmerge": "^4.2.2", - "detect-indent": "^6.0.0", - "js-beautify": "^1.13.13", - "plugin-error": "^1.0.1", + "deepmerge": "^4.3.1", + "detect-indent": "^6.1.0", + "js-beautify": "^1.14.11", + "plugin-error": "^2.0.1", "through2": "^4.0.2" } }, + "node_modules/gulp-json-editor/node_modules/plugin-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-2.0.1.tgz", + "integrity": "sha512-zMakqvIDyY40xHOvzXka0kUvf40nYIuwRE8dWhti2WtjQZ31xAgBZBhxsK7vK3QbRXS1Xms/LO7B5cuAsfB2Gg==", + "dependencies": { + "ansi-colors": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/gulp-json-editor/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3228,11 +3657,11 @@ } }, "node_modules/gulp-replace": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.3.tgz", - "integrity": "sha512-HcPHpWY4XdF8zxYkDODHnG2+7a3nD/Y8Mfu3aBgMiCFDW3X2GiOKXllsAmILcxe3KZT2BXoN18WrpEFm48KfLQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.4.tgz", + "integrity": "sha512-SVSF7ikuWKhpAW4l4wapAqPPSToJoiNKsbDoUnRrSgwZHH7lH8pbPeQj1aOVYQrbZKhfSVBxVW+Py7vtulRktw==", "dependencies": { - "@types/node": "^14.14.41", + "@types/node": "*", "@types/vinyl": "^2.0.4", "istextorbinary": "^3.0.0", "replacestream": "^4.0.3", @@ -3340,9 +3769,9 @@ } }, "node_modules/gulp-uglify/node_modules/uglify-js": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.16.0.tgz", - "integrity": "sha512-FEikl6bR30n0T3amyBh3LoiBdqHRy/f4H80+My34HOesOKyHfOsxAPAxOoqC0JUnC1amnO0IwkYC3sko51caSw==", + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -3445,7 +3874,7 @@ "node_modules/gulp-util/node_modules/vinyl": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "integrity": "sha512-P5zdf3WB9uzr7IFoVQ2wZTmUwHL8cMZWJGzLBNCHNZ3NB6HTMsYABtt7z8tAGIINLXyAob9B9a1yzVGMFOYKEA==", "dependencies": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -3466,17 +3895,6 @@ "node": ">= 0.10" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -3508,11 +3926,22 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3565,6 +3994,17 @@ "node": ">=0.10.0" } }, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -3592,6 +4032,18 @@ "node": ">=0.10.0" } }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/import-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/import-regex/-/import-regex-1.1.0.tgz", @@ -3754,22 +4206,14 @@ } }, "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", + "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", "dependencies": { - "kind-of": "^6.0.0" + "hasown": "^2.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" + "node": ">= 0.10" } }, "node_modules/is-arrayish": { @@ -3794,54 +4238,37 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "node_modules/is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", + "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", "dependencies": { - "kind-of": "^6.0.0" + "hasown": "^2.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/is-extendable": { @@ -4060,20 +4487,38 @@ "url": "https://bevry.me/fund" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" }, "node_modules/js-beautify": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.3.tgz", - "integrity": "sha512-f1ra8PHtOEu/70EBnmiUlV8nJePS58y9qKjl4JHfYWlFH6bo7ogZBz//FAZp7jDuXtYnGYKymZPlrg2I/9Zo4g==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", + "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", "dependencies": { "config-chain": "^1.1.13", - "editorconfig": "^0.15.3", - "glob": "^7.1.3", - "nopt": "^5.0.0" + "editorconfig": "^1.0.4", + "glob": "^10.3.3", + "js-cookie": "^3.0.5", + "nopt": "^7.2.0" }, "bin": { "css-beautify": "js/bin/css-beautify.js", @@ -4081,7 +4526,58 @@ "js-beautify": "js/bin/js-beautify.js" }, "engines": { - "node": ">=10" + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" } }, "node_modules/json-stable-stringify-without-jsonify": { @@ -4449,18 +4945,20 @@ } }, "node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, "node_modules/macos-release": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz", - "integrity": "sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.1.tgz", + "integrity": "sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==", "engines": { "node": ">=6" }, @@ -4468,6 +4966,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -4633,6 +5153,18 @@ "node": ">=0.10.0" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", @@ -4653,9 +5185,20 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } }, "node_modules/mixin-deep": { "version": "1.3.2", @@ -4728,6 +5271,12 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==" }, + "node_modules/nan": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "optional": true + }, "node_modules/nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -4791,6 +5340,12 @@ "node": ">=0.10.0" } }, + "node_modules/native-request": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/native-request/-/native-request-1.1.0.tgz", + "integrity": "sha512-uZ5rQaeRn15XmpgE0xoPL8YWqcX90VtCFglYwAgkvKM5e8fog+vePLAhHxuuv/gRkrQxIeh5U3q9sMNUrENqWw==", + "optional": true + }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", @@ -4802,9 +5357,9 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -4833,34 +5388,34 @@ } }, "node_modules/node-releases": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", - "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/node.extend": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", - "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.3.tgz", + "integrity": "sha512-xwADg/okH48PvBmRZyoX8i8GJaKuJ1CqlqotlZOhUio8egD1P5trJupHKBzcPjSF9ifK2gPcEICRBnkfPqQXZw==", "dependencies": { - "has": "^1.0.3", - "is": "^3.2.1" + "hasown": "^2.0.0", + "is": "^3.3.0" }, "engines": { "node": ">=0.4.0" } }, "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", + "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", "dependencies": { - "abbrev": "1" + "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": ">=6" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/normalize-package-data": { @@ -4957,47 +5512,16 @@ "node": ">=0.10.0" } }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/object-copy/node_modules/kind-of": { @@ -5031,13 +5555,13 @@ } }, "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, "engines": { @@ -5303,6 +5827,29 @@ "node": ">=0.10.0" } }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", @@ -5462,10 +6009,11 @@ "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "optional": true }, "node_modules/pump": { "version": "2.0.1", @@ -5512,9 +6060,9 @@ } }, "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -5525,6 +6073,11 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -5708,11 +6261,11 @@ "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==" }, "node_modules/resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dependencies": { - "is-core-module": "^2.8.1", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -5963,9 +6516,23 @@ } }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/safe-regex": { "version": "1.1.0", @@ -5981,9 +6548,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "bin": { "semver": "bin/semver" } @@ -6004,6 +6571,22 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, + "node_modules/set-function-length": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "dependencies": { + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -6053,11 +6636,6 @@ "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" }, - "node_modules/sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==" - }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -6138,61 +6716,16 @@ "node": ">=0.10.0" } }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/snapdragon/node_modules/source-map": { @@ -6239,18 +6772,18 @@ } }, "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", @@ -6262,9 +6795,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==" + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==" }, "node_modules/split-string": { "version": "3.1.0", @@ -6352,61 +6885,16 @@ "node": ">=0.10.0" } }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/stream-exhaust": { @@ -6415,9 +6903,9 @@ "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==" }, "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" }, "node_modules/string_decoder": { "version": "1.1.1", @@ -6427,6 +6915,11 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -6439,6 +6932,47 @@ "node": ">=4" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width/node_modules/ansi-regex": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", @@ -6482,6 +7016,26 @@ "node": ">=0.10.0" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", @@ -6667,7 +7221,7 @@ "node_modules/to-absolute-glob": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==", "dependencies": { "is-absolute": "^1.0.0", "is-negated-glob": "^1.0.0" @@ -6679,7 +7233,7 @@ "node_modules/to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", "dependencies": { "kind-of": "^3.0.2" }, @@ -6715,7 +7269,7 @@ "node_modules/to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", "dependencies": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" @@ -6761,7 +7315,7 @@ "node_modules/to-through": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==", "dependencies": { "through2": "^2.0.3" }, @@ -6781,7 +7335,7 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/tslib": { "version": "1.14.1", @@ -6789,19 +7343,19 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "integrity": "sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w==", "dependencies": { "source-map": "~0.5.1", "yargs": "~3.10.0" @@ -6845,7 +7399,7 @@ "node_modules/uglify-js/node_modules/yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "integrity": "sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==", "dependencies": { "camelcase": "^1.0.2", "cliui": "^2.1.0", @@ -6853,10 +7407,16 @@ "window-size": "0.1.0" } }, + "node_modules/uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q==", + "optional": true + }, "node_modules/unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", "engines": { "node": ">=0.10.0" } @@ -6884,11 +7444,16 @@ "node_modules/undertaker-registry": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "integrity": "sha512-UR1khWeAjugW3548EfQmL9Z7pGMlBgXteQpr1IZeZBtnkCJQJIJ1Scj0mb9wQaPvUZ9Q17XqW6TIaPchJkyfqw==", "engines": { "node": ">= 0.10" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -6923,7 +7488,7 @@ "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", "dependencies": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -6973,16 +7538,50 @@ "yarn": "*" } }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-browserslist-db/node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, "node_modules/urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", "deprecated": "Please see https://github.com/lydell/urix#deprecated" }, "node_modules/url-regex": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/url-regex/-/url-regex-3.2.0.tgz", - "integrity": "sha1-260eDJ4p4QXdCx8J9oYvf9tIJyQ=", + "integrity": "sha512-dQ9cJzMou5OKr6ZzfvwJkCq3rC72PNXhqz0v3EIhF4a3Np+ujr100AhUx2cKx5ei3iymoJpJrPB3sVSEMdqAeg==", "dependencies": { "ip-regex": "^1.0.1" }, @@ -7001,7 +7600,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/v8flags": { "version": "3.2.0", @@ -7026,7 +7625,7 @@ "node_modules/value-or-function": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==", "engines": { "node": ">= 0.10" } @@ -7086,7 +7685,7 @@ "node_modules/vinyl-sourcemap": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==", "dependencies": { "append-buffer": "^1.0.2", "convert-source-map": "^1.5.0", @@ -7114,7 +7713,7 @@ "node_modules/vinyl-sourcemaps-apply": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", - "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", "dependencies": { "source-map": "^0.5.1" } @@ -7130,12 +7729,12 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -7144,7 +7743,7 @@ "node_modules/when": { "version": "3.7.8", "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", - "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=" + "integrity": "sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw==" }, "node_modules/which": { "version": "1.3.1", @@ -7160,12 +7759,12 @@ "node_modules/which-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==" }, "node_modules/window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "integrity": "sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg==", "engines": { "node": ">= 0.8.0" } @@ -7187,7 +7786,7 @@ "node_modules/wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "integrity": "sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==", "engines": { "node": ">=0.4.0" } @@ -7195,7 +7794,7 @@ "node_modules/wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", "dependencies": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" @@ -7204,6 +7803,93 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -7231,12 +7917,12 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/wrench-sui": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wrench-sui/-/wrench-sui-0.0.3.tgz", - "integrity": "sha1-1hoSAwwf2NZxs90VqmyeD83E4sg=", + "integrity": "sha512-Y6qzMpcMG9akKnIdUsKzEF/Ht0KQJBP8ETkZj3FcGe93NC71e940WZUP1y+j+hc8Ecx9TyX0GvAWC4yymA88yA==", "engines": { "node": ">=0.1.97" } @@ -7255,9 +7941,9 @@ "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==" }, "node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yamljs": { "version": "0.3.0", @@ -7293,9 +7979,9 @@ } }, "node_modules/yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "engines": { "node": ">=12" } From f6c62cd07facf4a7c18a25c20630dbb7044be0eb Mon Sep 17 00:00:00 2001 From: GiteaBot <teabot@gitea.io> Date: Tue, 5 Mar 2024 00:23:19 +0000 Subject: [PATCH 09/62] [skip ci] Updated translations via Crowdin (cherry picked from commit 82875ae946b34e67beb3c89d0bd02a0fee9ad96e) --- options/locale/locale_ja-JP.ini | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index fcccac21cc..6f634e9bc0 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -150,8 +150,8 @@ footer.software=ソフトウェアについて footer.links=リンク [heatmap] -number_of_contributions_in_the_last_12_months=過去 12 か月間で %s 個の貢献 -no_contributions=貢献なし +number_of_contributions_in_the_last_12_months=過去 12 か月間で %s 件の実績 +no_contributions=実績なし less=少 more=多 @@ -1534,7 +1534,7 @@ issues.role.member_helper=このユーザーはこのリポジトリを所有し issues.role.collaborator=共同作業者 issues.role.collaborator_helper=このユーザーはリポジトリ上で共同作業するように招待されています。 issues.role.first_time_contributor=初めての貢献者 -issues.role.first_time_contributor_helper=これは、このユーザーのリポジトリへの最初の貢献です。 +issues.role.first_time_contributor_helper=これは、このユーザーによるリポジトリへの最初の貢献です。 issues.role.contributor=貢献者 issues.role.contributor_helper=このユーザーは以前にリポジトリにコミットしています。 issues.re_request_review=レビューを再依頼 @@ -2035,7 +2035,8 @@ settings.mirror_settings.docs.more_information_if_disabled=プッシュミラー 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.mirrored_repository=同期するリポジトリ +settings.mirror_settings.mirrored_repository=ミラー元のリポジトリ +settings.mirror_settings.pushed_repository=プッシュ先のリポジトリ settings.mirror_settings.direction=方向 settings.mirror_settings.direction.pull=プル settings.mirror_settings.direction.push=プッシュ @@ -3574,6 +3575,8 @@ runs.actors_no_select=すべてのアクター runs.status_no_select=すべてのステータス runs.no_results=一致する結果はありません。 runs.no_workflows=ワークフローはまだありません。 +runs.no_workflows.quick_start=Gitea Actions の始め方がわからない? では<a target="_blank" rel="noopener noreferrer" href="%s">クイックスタートガイド</a>をご覧ください。 +runs.no_workflows.documentation=Gitea Actions の詳細については、<a target="_blank" rel="noopener noreferrer" href="%s">ドキュメント</a>を参照してください。 runs.no_runs=ワークフローはまだ実行されていません。 runs.empty_commit_message=(空のコミットメッセージ) From 9482b023a815e54c79ee9994fe07d4c30d49bcff Mon Sep 17 00:00:00 2001 From: wxiaoguang <wxiaoguang@gmail.com> Date: Tue, 5 Mar 2024 10:12:03 +0800 Subject: [PATCH 10/62] Make "/user/login" page redirect if the current user has signed in (#29583) Fix #29582 and maybe more. Maybe fix #29116 (cherry picked from commit df1268ca08aaacae54c775a8eec34006dfe365e0) --- routers/web/auth/auth.go | 30 ++++++++++++------- routers/web/auth/auth_test.go | 43 +++++++++++++++++++++++++++ routers/web/repo/wiki_test.go | 2 +- services/contexttest/context_tests.go | 3 +- 4 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 routers/web/auth/auth_test.go diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 1adb891840..1c55256db4 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -135,9 +135,21 @@ func resetLocale(ctx *context.Context, u *user_model.User) error { return nil } +func RedirectAfterLogin(ctx *context.Context) { + redirectTo := ctx.FormString("redirect_to") + if redirectTo == "" { + redirectTo = ctx.GetSiteCookie("redirect_to") + } + middleware.DeleteRedirectToCookie(ctx.Resp) + nextRedirectTo := setting.AppSubURL + string(setting.LandingPageURL) + if setting.LandingPageURL == setting.LandingPageLogin { + nextRedirectTo = setting.AppSubURL + "/" // do not cycle-redirect to the login page + } + ctx.RedirectToFirst(redirectTo, nextRedirectTo) +} + func CheckAutoLogin(ctx *context.Context) bool { - // Check auto-login - isSucceed, err := autoSignIn(ctx) + isSucceed, err := autoSignIn(ctx) // try to auto-login if err != nil { ctx.ServerError("autoSignIn", err) return true @@ -146,17 +158,10 @@ func CheckAutoLogin(ctx *context.Context) bool { redirectTo := ctx.FormString("redirect_to") if len(redirectTo) > 0 { middleware.SetRedirectToCookie(ctx.Resp, redirectTo) - } else { - redirectTo = ctx.GetSiteCookie("redirect_to") } if isSucceed { - middleware.DeleteRedirectToCookie(ctx.Resp) - nextRedirectTo := setting.AppSubURL + string(setting.LandingPageURL) - if setting.LandingPageURL == setting.LandingPageLogin { - nextRedirectTo = setting.AppSubURL + "/" // do not cycle-redirect to the login page - } - ctx.RedirectToFirst(redirectTo, nextRedirectTo) + RedirectAfterLogin(ctx) return true } @@ -171,6 +176,11 @@ func SignIn(ctx *context.Context) { return } + if ctx.IsSigned { + RedirectAfterLogin(ctx) + return + } + oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true)) if err != nil { ctx.ServerError("UserSignIn", err) diff --git a/routers/web/auth/auth_test.go b/routers/web/auth/auth_test.go new file mode 100644 index 0000000000..c6afbf877c --- /dev/null +++ b/routers/web/auth/auth_test.go @@ -0,0 +1,43 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package auth + +import ( + "net/http" + "net/url" + "testing" + + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/services/contexttest" + + "github.com/stretchr/testify/assert" +) + +func TestUserLogin(t *testing.T) { + ctx, resp := contexttest.MockContext(t, "/user/login") + SignIn(ctx) + assert.Equal(t, http.StatusOK, resp.Code) + + ctx, resp = contexttest.MockContext(t, "/user/login") + ctx.IsSigned = true + SignIn(ctx) + assert.Equal(t, http.StatusSeeOther, resp.Code) + assert.Equal(t, "/", test.RedirectURL(resp)) + + ctx, resp = contexttest.MockContext(t, "/user/login?redirect_to=/other") + ctx.IsSigned = true + SignIn(ctx) + assert.Equal(t, "/other", test.RedirectURL(resp)) + + ctx, resp = contexttest.MockContext(t, "/user/login") + ctx.Req.AddCookie(&http.Cookie{Name: "redirect_to", Value: "/other-cookie"}) + ctx.IsSigned = true + SignIn(ctx) + assert.Equal(t, "/other-cookie", test.RedirectURL(resp)) + + ctx, resp = contexttest.MockContext(t, "/user/login?redirect_to="+url.QueryEscape("https://example.com")) + ctx.IsSigned = true + SignIn(ctx) + assert.Equal(t, "/", test.RedirectURL(resp)) +} diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go index 49c83cfef5..719cca3049 100644 --- a/routers/web/repo/wiki_test.go +++ b/routers/web/repo/wiki_test.go @@ -79,7 +79,7 @@ func assertPagesMetas(t *testing.T, expectedNames []string, metas any) { func TestWiki(t *testing.T) { unittest.PrepareTestEnv(t) - ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki/?action=_pages") + ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki") ctx.SetParams("*", "Home") contexttest.LoadRepo(t, ctx, 1) Wiki(ctx) diff --git a/services/contexttest/context_tests.go b/services/contexttest/context_tests.go index 431017a30d..d3e6de7efe 100644 --- a/services/contexttest/context_tests.go +++ b/services/contexttest/context_tests.go @@ -7,6 +7,7 @@ package contexttest import ( gocontext "context" "io" + "maps" "net/http" "net/http/httptest" "net/url" @@ -36,7 +37,7 @@ func mockRequest(t *testing.T, reqPath string) *http.Request { } requestURL, err := url.Parse(path) assert.NoError(t, err) - req := &http.Request{Method: method, URL: requestURL, Form: url.Values{}} + req := &http.Request{Method: method, URL: requestURL, Form: maps.Clone(requestURL.Query()), Header: http.Header{}} req = req.WithContext(middleware.WithContextData(req.Context())) return req } From 4f66fec707cd4bf8f76dd7475af9836dca3dd99b Mon Sep 17 00:00:00 2001 From: wxiaoguang <wxiaoguang@gmail.com> Date: Tue, 5 Mar 2024 11:03:14 +0800 Subject: [PATCH 11/62] Use flex wrap to layout the PR update button (#29590) Follow #29418 I think using "flex-wrap: wrap" here is better than hard-coding the screen width. By using "flex-wrap: wrap", the UI layouts automatically for various widths (even if in some languages, the sentence might be pretty long) (cherry picked from commit ade62416917bc87810991585d7047851834ee316) --- web_src/css/repo.css | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 87ce829a78..d60fb4db21 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -935,18 +935,11 @@ .repository.view.issue .comment-list .comment .merge-section .item-section { display: flex; + flex-wrap: wrap; align-items: center; justify-content: space-between; padding: 0; - margin-top: -0.25rem; - margin-bottom: -0.25rem; -} - -@media (max-width: 991.98px) { - .repository.view.issue .comment-list .comment .merge-section .item-section { - align-items: flex-start; - flex-direction: column; - } + gap: 0.5em; } .repository.view.issue .comment-list .comment .merge-section .divider { From 588104d9d18266bafaf2d46e279a36dacc991c75 Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Tue, 5 Mar 2024 05:31:29 +0100 Subject: [PATCH 12/62] Adjust tailwind content globs (#29596) Tailwind content is not going to appear in `web_src/css`, `web_src/fomantic` or `web_src/svg` or the JSON templates, so we don't need to have tailwind scan these directories which will speed up the build. --------- Co-authored-by: delvh <dev.lh@web.de> (cherry picked from commit 72b213b00fe1aa18d082423fbc4cfffff483eb18) --- tailwind.config.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tailwind.config.js b/tailwind.config.js index fb17980568..63a5387d19 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -29,8 +29,10 @@ export default { content: [ isProduction && '!./templates/devtest/**/*', isProduction && '!./web_src/js/standalone/devtest.js', + '!./templates/swagger/v1_json.tmpl', + '!./templates/user/auth/oidc_wellknown.tmpl', './templates/**/*.tmpl', - './web_src/**/*.{js,vue}', + './web_src/js/**/*.{js,vue}', ].filter(Boolean), blocklist: [ // classes that don't work without CSS variables from "@tailwind base" which we don't use From 5f650283121b843f839f996f69c24966863834b2 Mon Sep 17 00:00:00 2001 From: wxiaoguang <wxiaoguang@gmail.com> Date: Tue, 5 Mar 2024 12:59:16 +0800 Subject: [PATCH 13/62] Remove unnecessary ctxData for "attachments" template (#29600) The "attachments" template never uses it (cherry picked from commit 3e84bfdf410ec1fdf22711156d729e51d7355a8e) --- templates/repo/diff/comments.tmpl | 2 +- templates/repo/issue/view_content.tmpl | 2 +- templates/repo/issue/view_content/comments.tmpl | 4 ++-- templates/repo/issue/view_content/conversation.tmpl | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/repo/diff/comments.tmpl b/templates/repo/diff/comments.tmpl index 0bb577deba..465ed2b911 100644 --- a/templates/repo/diff/comments.tmpl +++ b/templates/repo/diff/comments.tmpl @@ -63,7 +63,7 @@ <div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div> <div class="edit-content-zone gt-hidden" data-update-url="{{$.root.RepoLink}}/comments/{{.ID}}" data-context="{{$.root.RepoLink}}" data-attachment-url="{{$.root.RepoLink}}/comments/{{.ID}}/attachments"></div> {{if .Attachments}} - {{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Attachments "RenderedContent" .RenderedContent}} + {{template "repo/issue/view_content/attachments" dict "Attachments" .Attachments "RenderedContent" .RenderedContent}} {{end}} </div> {{$reactions := .Reactions.GroupByType}} diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index 5ce54df058..980b4f373c 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -62,7 +62,7 @@ <div id="issue-{{.Issue.ID}}-raw" class="raw-content gt-hidden">{{.Issue.Content}}</div> <div class="edit-content-zone gt-hidden" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/content" data-context="{{.RepoLink}}" data-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/attachments" data-view-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/view-attachments"></div> {{if .Issue.Attachments}} - {{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Issue.Attachments "RenderedContent" .Issue.RenderedContent}} + {{template "repo/issue/view_content/attachments" dict "Attachments" .Issue.Attachments "RenderedContent" .Issue.RenderedContent}} {{end}} </div> {{$reactions := .Issue.Reactions.GroupByType}} diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index ccd771256d..bc2fd48e67 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -69,7 +69,7 @@ <div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div> <div class="edit-content-zone gt-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div> {{if .Attachments}} - {{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Attachments "RenderedContent" .RenderedContent}} + {{template "repo/issue/view_content/attachments" dict "Attachments" .Attachments "RenderedContent" .RenderedContent}} {{end}} </div> {{$reactions := .Reactions.GroupByType}} @@ -444,7 +444,7 @@ <div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div> <div class="edit-content-zone gt-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div> {{if .Attachments}} - {{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Attachments "RenderedContent" .RenderedContent}} + {{template "repo/issue/view_content/attachments" dict "Attachments" .Attachments "RenderedContent" .RenderedContent}} {{end}} </div> {{$reactions := .Reactions.GroupByType}} diff --git a/templates/repo/issue/view_content/conversation.tmpl b/templates/repo/issue/view_content/conversation.tmpl index 5bb99d1db6..b6e075d0ce 100644 --- a/templates/repo/issue/view_content/conversation.tmpl +++ b/templates/repo/issue/view_content/conversation.tmpl @@ -95,7 +95,7 @@ <div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div> <div class="edit-content-zone gt-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div> {{if .Attachments}} - {{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Attachments "RenderedContent" .RenderedContent}} + {{template "repo/issue/view_content/attachments" dict "Attachments" .Attachments "RenderedContent" .RenderedContent}} {{end}} </div> {{$reactions := .Reactions.GroupByType}} From 907c3d0c2067aa5e28defe75e36a155ca545b35a Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Tue, 5 Mar 2024 06:29:32 +0100 Subject: [PATCH 14/62] Replace more `gt-` with `tw-`, update frontend docs (#29595) Tested a few things, all working fine. Not sure if the chinese machine translation is good. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> (cherry picked from commit 7e8c1c5ba18e1ac8861f429b825163b8210fd178) Conflicts: docs/content/contributing/guidelines-frontend.zh-cn.md Gitea docs --- .../contributing/guidelines-frontend.en-us.md | 2 +- .../contributing/guidelines-frontend.zh-cn.md | 20 ++++++++++++++++--- templates/admin/emails/list.tmpl | 4 ++-- templates/admin/packages/list.tmpl | 4 ++-- templates/admin/user/list.tmpl | 2 +- templates/base/head_navbar.tmpl | 6 +++--- templates/base/paginate.tmpl | 2 +- templates/repo/commit_page.tmpl | 2 +- templates/repo/diff/box.tmpl | 2 +- templates/repo/diff/options_dropdown.tmpl | 4 ++-- templates/repo/diff/section_split.tmpl | 8 ++++---- templates/repo/diff/section_unified.tmpl | 2 +- templates/repo/diff/whitespace_dropdown.tmpl | 16 +++++++-------- templates/repo/issue/card.tmpl | 2 +- .../issue/labels/labels_selector_field.tmpl | 4 ++-- templates/repo/issue/new_form.tmpl | 2 +- .../repo/issue/view_content/sidebar.tmpl | 6 +++--- templates/repo/settings/branches.tmpl | 2 +- templates/status/500.tmpl | 2 +- web_src/css/helpers.css | 9 --------- web_src/js/features/repo-diff.js | 4 ++-- web_src/js/features/repo-issue.js | 4 ++-- web_src/js/features/repo-legacy.js | 6 +++--- web_src/js/features/user-auth.js | 4 ++-- web_src/js/markup/mermaid.js | 4 ++-- 25 files changed, 64 insertions(+), 59 deletions(-) diff --git a/docs/content/contributing/guidelines-frontend.en-us.md b/docs/content/contributing/guidelines-frontend.en-us.md index edd89e1231..2637780718 100644 --- a/docs/content/contributing/guidelines-frontend.en-us.md +++ b/docs/content/contributing/guidelines-frontend.en-us.md @@ -47,7 +47,7 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h 9. Avoid unnecessary `!important` in CSS, add comments to explain why it's necessary if it can't be avoided. 10. Avoid mixing different events in one event listener, prefer to use individual event listeners for every event. 11. Custom event names are recommended to use `ce-` prefix. -12. Gitea's tailwind-style CSS classes use `gt-` prefix (`gt-relative`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`). +12. Prefer using Tailwind CSS which is available via `tw-` prefix, e.g. `tw-relative`. Gitea's helper CSS classes use `gt-` prefix (`gt-df`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`). 13. Avoid inline scripts & styles as much as possible, it's recommended to put JS code into JS files and use CSS classes. If inline scripts & styles are unavoidable, explain the reason why it can't be avoided. ### Accessibility / ARIA diff --git a/docs/content/contributing/guidelines-frontend.zh-cn.md b/docs/content/contributing/guidelines-frontend.zh-cn.md index 66a4d4b4d6..ace0d97f49 100644 --- a/docs/content/contributing/guidelines-frontend.zh-cn.md +++ b/docs/content/contributing/guidelines-frontend.zh-cn.md @@ -34,7 +34,7 @@ HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。 我们推荐使用[Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html)和[Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html)。 -## Gitea 特定准则: +## Gitea 特定准则 1. 每个功能(Fomantic-UI/jQuery 模块)应放在单独的文件/目录中。 2. HTML 的 id 和 class 应使用 kebab-case,最好包含2-3个与功能相关的关键词。 @@ -47,7 +47,8 @@ HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。 9. 避免在 CSS 中使用不必要的`!important`,如果无法避免,添加注释解释为什么需要它。 10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用独立的事件监听器。 11. 推荐使用自定义事件名称前缀`ce-`。 -12. Gitea 的 tailwind-style CSS 类使用`gt-`前缀(`gt-relative`),而 Gitea 自身的私有框架级 CSS 类使用`g-`前缀(`g-modal-confirm`)。 +12. 建议使用 Tailwind CSS,它可以通过 `tw-` 前缀获得,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` 前缀(`gt-df`),Gitea 自身的私有框架级 CSS 类使用 `g-` 前缀(`g-modal-confirm`)。 +13. 尽量避免内联脚本和样式,建议将JS代码放入JS文件中并使用CSS类。如果内联脚本和样式不可避免,请解释无法避免的原因。 ### 可访问性 / ARIA @@ -64,18 +65,21 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari * Vue + Vanilla JS * Fomantic-UI(jQuery) +* htmx (部分页面重新加载其他静态组件) * Vanilla JS 不推荐的实现方式: * Vue + Fomantic-UI(jQuery) * jQuery + Vanilla JS +* htmx + 任何其他需要大量 JavaScript 代码或不必要的功能,如 htmx 脚本 (`hx-on`) 为了保持界面一致,Vue 组件可以使用 Fomantic-UI 的 CSS 类。 尽管不建议混合使用不同的框架, +我们使用 htmx 进行简单的交互。您可以在此 [PR](https://github.com/go-gitea/gitea/pull/28908) 中查看一个简单交互的示例,其中应使用 htmx。如果您需要更高级的反应性,请不要使用 htmx,请使用其他框架(Vue/Vanilla JS)。 但如果混合使用是必要的,并且代码设计良好且易于维护,也可以工作。 -### async 函数 +### `async` 函数 只有当函数内部存在`await`调用或返回`Promise`时,才将函数标记为`async`。 @@ -91,6 +95,12 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari 这是有意为之的,我们想调用异步函数并忽略Promise。 一些 lint 规则和 IDE 也会在未处理返回的 Promise 时发出警告。 +### 获取数据 + +要获取数据,请使用`modules/fetch.js`中的包装函数`GET`、`POST`等。他们 +接受内容的`data`选项,将自动设置 CSRF 令牌并返回 +[Response](https://developer.mozilla.org/en-US/docs/Web/API/Response)。 + ### HTML 属性和 dataset 禁止使用`dataset`,它的驼峰命名行为使得搜索属性变得困难。 @@ -132,3 +142,7 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari ### Vue3 和 JSX Gitea 现在正在使用 Vue3。我们决定不引入 JSX,以保持 HTML 代码和 JavaScript 代码分离。 + +### UI示例 + +Gitea 使用一些自制的 UI 元素并自定义其他元素,以将它们更好地集成到通用 UI 方法中。当在开发模式(`RUN_MODE=dev`)下运行 Gitea 时,在 `http(s)://your-gitea-url:port/devtest` 下会提供一个包含一些标准化 UI 示例的页面。 diff --git a/templates/admin/emails/list.tmpl b/templates/admin/emails/list.tmpl index bcd80368e6..29fbb5f039 100644 --- a/templates/admin/emails/list.tmpl +++ b/templates/admin/emails/list.tmpl @@ -47,8 +47,8 @@ {{range .Emails}} <tr> <td><a href="{{AppSubUrl}}/{{.Name | PathEscape}}">{{.Name}}</a></td> - <td class="gt-ellipsis gt-max-width-12rem">{{.FullName}}</td> - <td class="gt-ellipsis gt-max-width-12rem">{{.Email}}</td> + <td class="gt-ellipsis tw-max-w-48">{{.FullName}}</td> + <td class="gt-ellipsis tw-max-w-48">{{.Email}}</td> <td>{{if .IsPrimary}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td> <td> {{if .CanChange}} diff --git a/templates/admin/packages/list.tmpl b/templates/admin/packages/list.tmpl index 1f86803d55..aef4815424 100644 --- a/templates/admin/packages/list.tmpl +++ b/templates/admin/packages/list.tmpl @@ -62,8 +62,8 @@ {{end}} </td> <td>{{.Package.Type.Name}}</td> - <td class="gt-ellipsis gt-max-width-12rem">{{.Package.Name}}</td> - <td class="gt-ellipsis gt-max-width-12rem"><a href="{{.VersionWebLink}}">{{.Version.Version}}</a></td> + <td class="gt-ellipsis tw-max-w-48">{{.Package.Name}}</td> + <td class="gt-ellipsis tw-max-w-48"><a href="{{.VersionWebLink}}">{{.Version.Version}}</a></td> <td><a href="{{.Creator.HomeLink}}">{{.Creator.Name}}</a></td> <td> {{if .Repository}} diff --git a/templates/admin/user/list.tmpl b/templates/admin/user/list.tmpl index 8fdc80fc70..e9ce17ac90 100644 --- a/templates/admin/user/list.tmpl +++ b/templates/admin/user/list.tmpl @@ -96,7 +96,7 @@ <span class="ui mini label">{{ctx.Locale.Tr "admin.users.remote"}}</span> {{end}} </td> - <td class="gt-ellipsis gt-max-width-12rem">{{.Email}}</td> + <td class="gt-ellipsis tw-max-w-48">{{.Email}}</td> <td>{{if .IsActive}}{{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> diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index 06fc5913d0..e755775985 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -14,7 +14,7 @@ <div class="ui secondary menu item navbar-mobile-right"> {{if .IsSigned}} <a id="mobile-notifications-icon" class="item tw-w-auto gt-p-3" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}"> - <div class="gt-relative"> + <div class="tw-relative"> {{svg "octicon-bell"}} <span class="notification_count{{if not $notificationUnreadCount}} gt-hidden{{end}}">{{$notificationUnreadCount}}</span> </div> @@ -76,7 +76,7 @@ {{else if .IsSigned}} {{if EnableTimetracking}} <a class="active-stopwatch-trigger item gt-mx-0{{if not .ActiveStopwatch}} gt-hidden{{end}}" href="{{.ActiveStopwatch.IssueLink}}" title="{{ctx.Locale.Tr "active_stopwatch"}}"> - <div class="gt-relative"> + <div class="tw-relative"> {{svg "octicon-stopwatch"}} <span class="header-stopwatch-dot"></span> </div> @@ -112,7 +112,7 @@ {{end}} <a class="item not-mobile gt-mx-0" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}"> - <div class="gt-relative"> + <div class="tw-relative"> {{svg "octicon-bell"}} <span class="notification_count{{if not $notificationUnreadCount}} gt-hidden{{end}}">{{$notificationUnreadCount}}</span> </div> diff --git a/templates/base/paginate.tmpl b/templates/base/paginate.tmpl index ef7d0b341b..8c2adc1f94 100644 --- a/templates/base/paginate.tmpl +++ b/templates/base/paginate.tmpl @@ -17,7 +17,7 @@ {{if eq .Num -1}} <a class="disabled item">...</a> {{else}} - <a class="{{if .IsCurrent}}active {{end}}item gt-content-center" {{if not .IsCurrent}}href="{{$paginationLink}}?page={{.Num}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>{{.Num}}</a> + <a class="{{if .IsCurrent}}active {{end}}item tw-content-center" {{if not .IsCurrent}}href="{{$paginationLink}}?page={{.Num}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>{{.Num}}</a> {{end}} {{end}} <a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$paginationLink}}?page={{.Next}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}> diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index 1d900cdefb..80af73ce48 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -17,7 +17,7 @@ {{$class = (print $class " isWarning")}} {{end}} {{end}} - <div class="ui top attached header clearing segment gt-relative commit-header {{$class}}"> + <div class="ui top attached header clearing segment tw-relative commit-header {{$class}}"> <div class="gt-df gt-mb-4 gt-fw"> <h3 class="gt-mb-0 gt-f1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3> {{if not $.PageIsWiki}} diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 4cfe9034c8..40c163c555 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -112,7 +112,7 @@ <div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}} gt-mt-0" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}> <h4 class="diff-file-header sticky-2nd-row ui top attached normal header gt-df gt-ac gt-sb gt-fw"> <div class="diff-file-name gt-df gt-ac gt-gap-2 gt-fw"> - <button class="fold-file btn interact-bg gt-p-2{{if not $isExpandable}} gt-invisible{{end}}"> + <button class="fold-file btn interact-bg gt-p-2{{if not $isExpandable}} tw-invisible{{end}}"> {{if $file.ShouldBeHidden}} {{svg "octicon-chevron-right" 18}} {{else}} diff --git a/templates/repo/diff/options_dropdown.tmpl b/templates/repo/diff/options_dropdown.tmpl index b7c46dd846..09b7b80e41 100644 --- a/templates/repo/diff/options_dropdown.tmpl +++ b/templates/repo/diff/options_dropdown.tmpl @@ -17,13 +17,13 @@ {{if .Issue.Index}} {{if .ShowOutdatedComments}} <a class="item" href="?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated=false"> - <label class="gt-pointer-events-none"> + <label class="tw-pointer-events-none"> {{ctx.Locale.Tr "repo.issues.review.option.hide_outdated_comments"}} </label> </a> {{else}} <a class="item" href="?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated=true"> - <label class="gt-pointer-events-none"> + <label class="tw-pointer-events-none"> {{ctx.Locale.Tr "repo.issues.review.option.show_outdated_comments"}} </label> </a> diff --git a/templates/repo/diff/section_split.tmpl b/templates/repo/diff/section_split.tmpl index 5a1c8cce64..00e23f5c1c 100644 --- a/templates/repo/diff/section_split.tmpl +++ b/templates/repo/diff/section_split.tmpl @@ -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-code lines-code-old del-code">{{/* */}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/* - */}}<button type="button" aria-label="{{ctx.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}}">{{/* + */}}<button type="button" aria-label="{{ctx.Locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} tw-invisible{{end}}" data-side="left" data-idx="{{$line.LeftIdx}}">{{/* */}}{{svg "octicon-plus"}}{{/* */}}</button>{{/* */}}{{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-code lines-code-new add-code">{{/* */}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/* - */}}<button type="button" aria-label="{{ctx.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}}">{{/* + */}}<button type="button" aria-label="{{ctx.Locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-right{{if (not $match.CanComment)}} tw-invisible{{end}}" data-side="right" data-idx="{{$match.RightIdx}}">{{/* */}}{{svg "octicon-plus"}}{{/* */}}</button>{{/* */}}{{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-code lines-code-old">{{/* */}}{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 2))}}{{/* - */}}<button type="button" aria-label="{{ctx.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}}">{{/* + */}}<button type="button" aria-label="{{ctx.Locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} tw-invisible{{end}}" data-side="left" data-idx="{{$line.LeftIdx}}">{{/* */}}{{svg "octicon-plus"}}{{/* */}}</button>{{/* */}}{{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-code lines-code-new">{{/* */}}{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 3))}}{{/* - */}}<button type="button" aria-label="{{ctx.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}}">{{/* + */}}<button type="button" aria-label="{{ctx.Locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-right{{if (not $line.CanComment)}} tw-invisible{{end}}" data-side="right" data-idx="{{$line.RightIdx}}">{{/* */}}{{svg "octicon-plus"}}{{/* */}}</button>{{/* */}}{{end}}{{/* diff --git a/templates/repo/diff/section_unified.tmpl b/templates/repo/diff/section_unified.tmpl index ae97dc6db6..e3249c26d5 100644 --- a/templates/repo/diff/section_unified.tmpl +++ b/templates/repo/diff/section_unified.tmpl @@ -52,7 +52,7 @@ {{else}} <td class="chroma lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}">{{/* */}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/* - */}}<button type="button" aria-label="{{ctx.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}}">{{/* + */}}<button type="button" aria-label="{{ctx.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)}} tw-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"}}{{/* */}}</button>{{/* */}}{{end}}{{/* diff --git a/templates/repo/diff/whitespace_dropdown.tmpl b/templates/repo/diff/whitespace_dropdown.tmpl index 7bf2ac9aec..cfabf836d6 100644 --- a/templates/repo/diff/whitespace_dropdown.tmpl +++ b/templates/repo/diff/whitespace_dropdown.tmpl @@ -2,26 +2,26 @@ {{svg "gitea-whitespace"}} <div class="menu"> <a class="item" href="?style={{if .IsSplitStyle}}split{{else}}unified{{end}}&whitespace=show-all&show-outdated={{$.ShowOutdatedComments}}"> - <label class="gt-pointer-events-none"> - <input class="gt-mr-3 gt-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "show-all"}} checked{{end}}> + <label class="tw-pointer-events-none"> + <input class="gt-mr-3 tw-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "show-all"}} checked{{end}}> {{ctx.Locale.Tr "repo.diff.whitespace_show_everything"}} </label> </a> <a class="item" href="?style={{if .IsSplitStyle}}split{{else}}unified{{end}}&whitespace=ignore-all&show-outdated={{$.ShowOutdatedComments}}"> - <label class="gt-pointer-events-none"> - <input class="gt-mr-3 gt-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "ignore-all"}} checked{{end}}> + <label class="tw-pointer-events-none"> + <input class="gt-mr-3 tw-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "ignore-all"}} checked{{end}}> {{ctx.Locale.Tr "repo.diff.whitespace_ignore_all_whitespace"}} </label> </a> <a class="item" href="?style={{if .IsSplitStyle}}split{{else}}unified{{end}}&whitespace=ignore-change&show-outdated={{$.ShowOutdatedComments}}"> - <label class="gt-pointer-events-none"> - <input class="gt-mr-3 gt-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "ignore-change"}} checked{{end}}> + <label class="tw-pointer-events-none"> + <input class="gt-mr-3 tw-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "ignore-change"}} checked{{end}}> {{ctx.Locale.Tr "repo.diff.whitespace_ignore_amount_changes"}} </label> </a> <a class="item" href="?style={{if .IsSplitStyle}}split{{else}}unified{{end}}&whitespace=ignore-eol&show-outdated={{$.ShowOutdatedComments}}"> - <label class="gt-pointer-events-none"> - <input class="gt-mr-3 gt-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "ignore-eol"}} checked{{end}}> + <label class="tw-pointer-events-none"> + <input class="gt-mr-3 tw-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "ignore-eol"}} checked{{end}}> {{ctx.Locale.Tr "repo.diff.whitespace_ignore_at_eol"}} </label> </a> diff --git a/templates/repo/issue/card.tmpl b/templates/repo/issue/card.tmpl index 5e524079c8..ff635c736a 100644 --- a/templates/repo/issue/card.tmpl +++ b/templates/repo/issue/card.tmpl @@ -7,7 +7,7 @@ </div> {{end}} <div class="content gt-p-0 tw-w-full"> - <div class="gt-df gt-items-start"> + <div class="gt-df tw-items-start"> <div class="issue-card-icon"> {{template "shared/issueicon" .}} </div> diff --git a/templates/repo/issue/labels/labels_selector_field.tmpl b/templates/repo/issue/labels/labels_selector_field.tmpl index d24dac46eb..e42a1de895 100644 --- a/templates/repo/issue/labels/labels_selector_field.tmpl +++ b/templates/repo/issue/labels/labels_selector_field.tmpl @@ -21,7 +21,7 @@ <div class="divider"></div> {{end}} {{$previousExclusiveScope = $exclusiveScope}} - <a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" {{if .IsArchived}}data-is-archived{{end}} 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> {{RenderLabel $.Context .}} + <a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" {{if .IsArchived}}data-is-archived{{end}} data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}tw-invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span> {{RenderLabel $.Context .}} {{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}} <p class="archived-label-hint">{{template "repo/issue/labels/label_archived" .}}</p> </a> @@ -34,7 +34,7 @@ <div class="divider"></div> {{end}} {{$previousExclusiveScope = $exclusiveScope}} - <a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" {{if .IsArchived}}data-is-archived{{end}} 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> {{RenderLabel $.Context .}} + <a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" {{if .IsArchived}}data-is-archived{{end}} data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}tw-invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span> {{RenderLabel $.Context .}} {{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}} <p class="archived-label-hint">{{template "repo/issue/labels/label_archived" .}}</p> </a> diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index e67314bfd5..b2b9e308f5 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -156,7 +156,7 @@ <div class="no-select item">{{ctx.Locale.Tr "repo.issues.new.clear_assignees"}}</div> {{range .Assignees}} <a class="item muted" href="#" data-id="{{.ID}}" data-id-selector="#assignee_{{.ID}}"> - <span class="octicon-check gt-invisible">{{svg "octicon-check"}}</span> + <span class="octicon-check tw-invisible">{{svg "octicon-check"}}</span> <span class="text"> {{ctx.AvatarUtils.Avatar . 28 "gt-mr-3"}}{{template "repo/search_name" .}} </span> diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index f5b6751d6d..329a39dd69 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -20,7 +20,7 @@ {{range .Reviewers}} {{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="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}> - <span class="octicon-check {{if not .Checked}}gt-invisible{{end}}">{{svg "octicon-check"}}</span> + <span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check"}}</span> <span class="text"> {{ctx.AvatarUtils.Avatar .User 28 "gt-mr-3"}}{{template "repo/search_name" .User}} </span> @@ -35,7 +35,7 @@ {{range .TeamReviewers}} {{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="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}> - <span class="octicon-check {{if not .Checked}}gt-invisible{{end}}">{{svg "octicon-check" 16}}</span> + <span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check" 16}}</span> <span class="text"> {{svg "octicon-people" 16 "gt-ml-4 gt-mr-2"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}} </span> @@ -231,7 +231,7 @@ {{$checked = true}} {{end}} {{end}} - <span class="octicon-check {{if not $checked}}gt-invisible{{end}}">{{svg "octicon-check"}}</span> + <span class="octicon-check {{if not $checked}}tw-invisible{{end}}">{{svg "octicon-check"}}</span> <span class="text"> {{ctx.AvatarUtils.Avatar . 20 "gt-mr-3"}}{{template "repo/search_name" .}} </span> diff --git a/templates/repo/settings/branches.tmpl b/templates/repo/settings/branches.tmpl index f8896b504e..78421ec009 100644 --- a/templates/repo/settings/branches.tmpl +++ b/templates/repo/settings/branches.tmpl @@ -16,7 +16,7 @@ {{.CsrfTokenHtml}} <input type="hidden" name="action" value="default_branch"> {{if not .Repository.IsEmpty}} - <div class="ui dropdown selection gt-f1 gt-mr-3 gt-max-width-24rem"> + <div class="ui dropdown selection gt-f1 gt-mr-3 tw-max-w-96"> {{svg "octicon-triangle-down" 14 "dropdown icon"}} <input type="hidden" name="branch" value="{{.Repository.DefaultBranch}}"> <div class="default text">{{.Repository.DefaultBranch}}</div> diff --git a/templates/status/500.tmpl b/templates/status/500.tmpl index fb159ea361..3825d9de4d 100644 --- a/templates/status/500.tmpl +++ b/templates/status/500.tmpl @@ -41,7 +41,7 @@ <div class="ui container gt-my-5"> {{if .ErrorMsg}} <p>{{ctx.Locale.Tr "error.occurred"}}:</p> - <pre class="tw-whitespace-pre-wrap gt-break-all">{{.ErrorMsg}}</pre> + <pre class="tw-whitespace-pre-wrap tw-break-all">{{.ErrorMsg}}</pre> {{end}} <div class="center gt-mt-5"> {{if or .SignedUser.IsAdmin .ShowFooterVersion}}<p>{{ctx.Locale.Tr "admin.config.app_ver"}}: {{AppVer}}</p>{{end}} diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css index 71f3a619b9..dad0f9b127 100644 --- a/web_src/css/helpers.css +++ b/web_src/css/helpers.css @@ -46,16 +46,7 @@ Gitea's private styles use `g-` prefix. text-overflow: ellipsis; } -.gt-max-width-12rem { max-width: 12rem !important; } -.gt-max-width-24rem { max-width: 24rem !important; } - /* below class names match Tailwind CSS */ -.gt-break-all { word-break: break-all !important; } -.gt-content-center { align-content: center !important; } -.gt-invisible { visibility: hidden !important; } -.gt-items-start { align-items: flex-start !important; } -.gt-pointer-events-none { pointer-events: none !important; } -.gt-relative { position: relative !important; } .gt-object-contain { object-fit: contain !important; } .gt-no-underline { text-decoration-line: none !important; } .gt-normal-case { text-transform: none !important; } diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js index 85cb66c728..d1071a3b36 100644 --- a/web_src/js/features/repo-diff.js +++ b/web_src/js/features/repo-diff.js @@ -71,9 +71,9 @@ function initRepoDiffConversationForm() { $form.closest('.conversation-holder').replaceWith($newConversationHolder); if ($form.closest('tr').data('line-type') === 'same') { - $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).addClass('gt-invisible'); + $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).addClass('tw-invisible'); } else { - $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('gt-invisible'); + $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('tw-invisible'); } $newConversationHolder.find('.dropdown').dropdown(); initCompReactionSelector($newConversationHolder); diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 10faeb135d..6fb13b0dda 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -180,9 +180,9 @@ export function initRepoIssueCommentDelete() { const idx = $conversationHolder.data('idx'); const lineType = $conversationHolder.closest('tr').data('line-type'); if (lineType === 'same') { - $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).removeClass('gt-invisible'); + $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).removeClass('tw-invisible'); } else { - $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).removeClass('gt-invisible'); + $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).removeClass('tw-invisible'); } $conversationHolder.remove(); } diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 10ad836797..8fcc78c177 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -150,7 +150,7 @@ export function initRepoCommentForm() { if ($(this).hasClass('checked')) { $(this).removeClass('checked'); - $(this).find('.octicon-check').addClass('gt-invisible'); + $(this).find('.octicon-check').addClass('tw-invisible'); if (hasUpdateAction) { if (!($(this).data('id') in items)) { items[$(this).data('id')] = { @@ -164,7 +164,7 @@ export function initRepoCommentForm() { } } else { $(this).addClass('checked'); - $(this).find('.octicon-check').removeClass('gt-invisible'); + $(this).find('.octicon-check').removeClass('tw-invisible'); if (hasUpdateAction) { if (!($(this).data('id') in items)) { items[$(this).data('id')] = { @@ -218,7 +218,7 @@ export function initRepoCommentForm() { $(this).parent().find('.item').each(function () { $(this).removeClass('checked'); - $(this).find('.octicon-check').addClass('gt-invisible'); + $(this).find('.octicon-check').addClass('tw-invisible'); }); if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { diff --git a/web_src/js/features/user-auth.js b/web_src/js/features/user-auth.js index 60d186e699..a871ac471c 100644 --- a/web_src/js/features/user-auth.js +++ b/web_src/js/features/user-auth.js @@ -9,13 +9,13 @@ export function initUserAuthOauth2() { for (const link of outer.querySelectorAll('.oauth-login-link')) { link.addEventListener('click', () => { - inner.classList.add('gt-invisible'); + inner.classList.add('tw-invisible'); outer.classList.add('is-loading'); setTimeout(() => { // recover previous content to let user try again // usually redirection will be performed before this action outer.classList.remove('is-loading'); - inner.classList.remove('gt-invisible'); + inner.classList.remove('tw-invisible'); }, 5000); }); } diff --git a/web_src/js/markup/mermaid.js b/web_src/js/markup/mermaid.js index 84d88a94c3..82e9909fec 100644 --- a/web_src/js/markup/mermaid.js +++ b/web_src/js/markup/mermaid.js @@ -45,7 +45,7 @@ export async function renderMermaid() { const {svg} = await mermaid.render('mermaid', source); const iframe = document.createElement('iframe'); - iframe.classList.add('markup-render', 'gt-invisible'); + iframe.classList.add('markup-render', 'tw-invisible'); iframe.srcdoc = `<html><head><style>${iframeCss}</style></head><body>${svg}</body></html>`; const mermaidBlock = document.createElement('div'); @@ -62,7 +62,7 @@ export async function renderMermaid() { iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`; setTimeout(() => { // avoid flash of iframe background mermaidBlock.classList.remove('is-loading'); - iframe.classList.remove('gt-invisible'); + iframe.classList.remove('tw-invisible'); }, 0); }); From b6057a34db38e563473db00543a1e39fd743ca34 Mon Sep 17 00:00:00 2001 From: Zettat123 <zettat123@gmail.com> Date: Tue, 5 Mar 2024 13:55:47 +0800 Subject: [PATCH 15/62] Skip email domain check when admin users adds user manually (#29522) Fix #27457 Administrators should be able to manually create any user even if the user's email address is not in `EMAIL_DOMAIN_ALLOWLIST`. (cherry picked from commit 4fd9c56ed09b31e2f6164a5f534a31c6624d0478) --- models/user/email_address.go | 75 ++++++++++++++++++----------- models/user/user.go | 20 +++++++- routers/api/v1/admin/user.go | 2 +- routers/web/admin/users.go | 2 +- tests/integration/api_admin_test.go | 26 ++++++++++ 5 files changed, 93 insertions(+), 32 deletions(-) diff --git a/models/user/email_address.go b/models/user/email_address.go index 6f3d5b1dde..80dc7a7971 100644 --- a/models/user/email_address.go +++ b/models/user/email_address.go @@ -157,37 +157,18 @@ func UpdateEmailAddress(ctx context.Context, email *EmailAddress) error { var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") -// ValidateEmail check if email is a allowed address +// ValidateEmail check if email is a valid & allowed address func ValidateEmail(email string) error { - if len(email) == 0 { - return ErrEmailInvalid{email} + if err := validateEmailBasic(email); err != nil { + return err } + return validateEmailDomain(email) +} - if !emailRegexp.MatchString(email) { - return ErrEmailCharIsNotSupported{email} - } - - if email[0] == '-' { - return ErrEmailInvalid{email} - } - - if _, err := mail.ParseAddress(email); err != nil { - return ErrEmailInvalid{email} - } - - // if there is no allow list, then check email against block list - if len(setting.Service.EmailDomainAllowList) == 0 && - validation.IsEmailDomainListed(setting.Service.EmailDomainBlockList, email) { - return ErrEmailInvalid{email} - } - - // if there is an allow list, then check email against allow list - if len(setting.Service.EmailDomainAllowList) > 0 && - !validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email) { - return ErrEmailInvalid{email} - } - - return nil +// ValidateEmailForAdmin check if email is a valid address when admins manually add users +func ValidateEmailForAdmin(email string) error { + return validateEmailBasic(email) + // In this case we do not need to check the email domain } func GetEmailAddressByEmail(ctx context.Context, email string) (*EmailAddress, error) { @@ -543,3 +524,41 @@ func ActivateUserEmail(ctx context.Context, userID int64, email string, activate return committer.Commit() } + +// validateEmailBasic checks whether the email complies with the rules +func validateEmailBasic(email string) error { + if len(email) == 0 { + return ErrEmailInvalid{email} + } + + if !emailRegexp.MatchString(email) { + return ErrEmailCharIsNotSupported{email} + } + + if email[0] == '-' { + return ErrEmailInvalid{email} + } + + if _, err := mail.ParseAddress(email); err != nil { + return ErrEmailInvalid{email} + } + + return nil +} + +// validateEmailDomain checks whether the email domain is allowed or blocked +func validateEmailDomain(email string) error { + // if there is no allow list, then check email against block list + if len(setting.Service.EmailDomainAllowList) == 0 && + validation.IsEmailDomainListed(setting.Service.EmailDomainBlockList, email) { + return ErrEmailInvalid{email} + } + + // if there is an allow list, then check email against allow list + if len(setting.Service.EmailDomainAllowList) > 0 && + !validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email) { + return ErrEmailInvalid{email} + } + + return nil +} diff --git a/models/user/user.go b/models/user/user.go index 710fab9f4e..6d8eea6585 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -598,6 +598,16 @@ type CreateUserOverwriteOptions struct { // CreateUser creates record of a new user. func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { + return createUser(ctx, u, false, overwriteDefault...) +} + +// AdminCreateUser is used by admins to manually create users +func AdminCreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { + return createUser(ctx, u, true, overwriteDefault...) +} + +// createUser creates record of a new user. +func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { if err = IsUsableUsername(u.Name); err != nil { return err } @@ -651,8 +661,14 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve return err } - if err := ValidateEmail(u.Email); err != nil { - return err + if createdByAdmin { + if err := ValidateEmailForAdmin(u.Email); err != nil { + return err + } + } else { + if err := ValidateEmail(u.Email); err != nil { + return err + } } ctx, committer, err := db.TxContext(ctx) diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 64315108b0..7f4200f684 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -133,7 +133,7 @@ func CreateUser(ctx *context.APIContext) { u.UpdatedUnix = u.CreatedUnix } - if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil { + if err := user_model.AdminCreateUser(ctx, u, overwriteDefault); err != nil { if user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err) || db.IsErrNameReserved(err) || diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index a34e0d0f0d..dfb103c8ab 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -177,7 +177,7 @@ func NewUserPost(ctx *context.Context) { u.MustChangePassword = form.MustChangePassword } - if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil { + if err := user_model.AdminCreateUser(ctx, u, overwriteDefault); err != nil { switch { case user_model.IsErrUserAlreadyExist(err): ctx.Data["Err_UserName"] = true diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index 0748a75ba4..53bdd11afd 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -14,9 +14,11 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/tests" + "github.com/gobwas/glob" "github.com/stretchr/testify/assert" ) @@ -333,3 +335,27 @@ func TestAPICron(t *testing.T) { } }) } + +func TestAPICreateUser_NotAllowedEmailDomain(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")} + defer func() { + setting.Service.EmailDomainAllowList = []glob.Glob{} + }() + + adminUsername := "user1" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) + + req := NewRequestWithValues(t, "POST", "/api/v1/admin/users", map[string]string{ + "email": "allowedUser1@example1.org", + "login_name": "allowedUser1", + "username": "allowedUser1", + "password": "allowedUser1_pass", + "must_change_password": "true", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "DELETE", "/api/v1/admin/users/allowedUser1").AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) +} From 02384ff9adc4ab65202b1da65ab6ee7b87c491dc Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Tue, 5 Mar 2024 15:21:52 +0100 Subject: [PATCH 16/62] Fix contributor graphs mobile layout and responsiveness (#29597) Also removed a unneeded and actually conflicting class name `stats-table`. Fixes: https://github.com/go-gitea/gitea/issues/29192 <img width="445" alt="image" src="https://github.com/go-gitea/gitea/assets/115237/787804ed-6ba4-437f-b314-f23cbe2edf7a"> (cherry picked from commit f14779592494d41b3ab04caaab53487f2f4ede5a) --- web_src/js/components/RepoContributors.vue | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/web_src/js/components/RepoContributors.vue b/web_src/js/components/RepoContributors.vue index 84fdcae1f6..22c247ae32 100644 --- a/web_src/js/components/RepoContributors.vue +++ b/web_src/js/components/RepoContributors.vue @@ -303,7 +303,7 @@ export default { </script> <template> <div> - <h2 class="ui header gt-df gt-ac gt-sb"> + <div class="ui header gt-df gt-ac gt-sb"> <div> <relative-time v-if="xAxisMin > 0" @@ -334,7 +334,7 @@ export default { <div class="ui dropdown jump" id="repo-contributors"> <div class="ui basic compact button"> <span class="text"> - {{ locale.filterLabel }} <strong>{{ locale.contributionType[type] }}</strong> + <span class="not-mobile">{{ locale.filterLabel }} </span><strong>{{ locale.contributionType[type] }}</strong> <svg-icon name="octicon-triangle-down" :size="14"/> </span> </div> @@ -351,7 +351,7 @@ export default { </div> </div> </div> - </h2> + </div> <div class="gt-df ui segment main-graph"> <div v-if="isLoading || errorText !== ''" class="gt-tc gt-m-auto"> <div v-if="isLoading"> @@ -370,7 +370,8 @@ export default { </div> <div class="contributor-grid"> <div - v-for="(contributor, index) in sortedContributors" :key="index" class="stats-table" + v-for="(contributor, index) in sortedContributors" + :key="index" v-memo="[sortedContributors, type]" > <div class="ui top attached header gt-df gt-f1"> @@ -406,13 +407,25 @@ export default { <style scoped> .main-graph { height: 260px; + padding-top: 2px; } + .contributor-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; } +.contributor-grid > * { + min-width: 0; +} + +@media (max-width: 991.98px) { + .contributor-grid { + grid-template-columns: repeat(1, 1fr); + } +} + .contributor-name { margin-bottom: 0; } From e7afba21ce2b02eb4230ba03752bd8b937f3e6ef Mon Sep 17 00:00:00 2001 From: Zettat123 <zettat123@gmail.com> Date: Wed, 6 Mar 2024 00:51:56 +0800 Subject: [PATCH 17/62] Skip email domain check when admins edit user emails (#29609) Follow #29522 Administrators should be able to set a user's email address even if the email address is not in `EMAIL_DOMAIN_ALLOWLIST` (cherry picked from commit 136dd99e86eea9c8bfe61b972a12b395655171e8) --- models/user/email_address.go | 2 +- routers/api/v1/admin/user.go | 2 +- routers/web/admin/users.go | 2 +- services/user/email.go | 5 +++-- services/user/email_test.go | 22 ++++++++++++++++++---- tests/integration/api_admin_test.go | 29 +++++++++++++++++++++++++++++ 6 files changed, 53 insertions(+), 9 deletions(-) diff --git a/models/user/email_address.go b/models/user/email_address.go index 80dc7a7971..1d90b127bf 100644 --- a/models/user/email_address.go +++ b/models/user/email_address.go @@ -165,7 +165,7 @@ func ValidateEmail(email string) error { return validateEmailDomain(email) } -// ValidateEmailForAdmin check if email is a valid address when admins manually add users +// ValidateEmailForAdmin check if email is a valid address when admins manually add or edit users func ValidateEmailForAdmin(email string) error { return validateEmailBasic(email) // In this case we do not need to check the email domain diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 7f4200f684..986305d423 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -209,7 +209,7 @@ func EditUser(ctx *context.APIContext) { } if form.Email != nil { - if err := user_service.AddOrSetPrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil { + if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil { switch { case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err): ctx.Error(http.StatusBadRequest, "EmailInvalid", err) diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index dfb103c8ab..671a0d8885 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -412,7 +412,7 @@ func EditUserPost(ctx *context.Context) { } if form.Email != "" { - if err := user_service.AddOrSetPrimaryEmailAddress(ctx, u, form.Email); err != nil { + if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, u, form.Email); err != nil { switch { case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err): ctx.Data["Err_Email"] = true diff --git a/services/user/email.go b/services/user/email.go index 0b579cf792..9dc5270842 100644 --- a/services/user/email.go +++ b/services/user/email.go @@ -14,12 +14,13 @@ import ( "code.gitea.io/gitea/modules/util" ) -func AddOrSetPrimaryEmailAddress(ctx context.Context, u *user_model.User, emailStr string) error { +// AdminAddOrSetPrimaryEmailAddress is used by admins to add or set a user's primary email address +func AdminAddOrSetPrimaryEmailAddress(ctx context.Context, u *user_model.User, emailStr string) error { if strings.EqualFold(u.Email, emailStr) { return nil } - if err := user_model.ValidateEmail(emailStr); err != nil { + if err := user_model.ValidateEmailForAdmin(emailStr); err != nil { return err } diff --git a/services/user/email_test.go b/services/user/email_test.go index 66d4821346..0784b4f803 100644 --- a/services/user/email_test.go +++ b/services/user/email_test.go @@ -10,11 +10,13 @@ import ( organization_model "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + "github.com/gobwas/glob" "github.com/stretchr/testify/assert" ) -func TestAddOrSetPrimaryEmailAddress(t *testing.T) { +func TestAdminAddOrSetPrimaryEmailAddress(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 27}) @@ -28,7 +30,7 @@ func TestAddOrSetPrimaryEmailAddress(t *testing.T) { assert.NotEqual(t, "new-primary@example.com", primary.Email) assert.Equal(t, user.Email, primary.Email) - assert.NoError(t, AddOrSetPrimaryEmailAddress(db.DefaultContext, user, "new-primary@example.com")) + assert.NoError(t, AdminAddOrSetPrimaryEmailAddress(db.DefaultContext, user, "new-primary@example.com")) primary, err = user_model.GetPrimaryEmailAddressOfUser(db.DefaultContext, user.ID) assert.NoError(t, err) @@ -39,7 +41,19 @@ func TestAddOrSetPrimaryEmailAddress(t *testing.T) { assert.NoError(t, err) assert.Len(t, emails, 2) - assert.NoError(t, AddOrSetPrimaryEmailAddress(db.DefaultContext, user, "user27@example.com")) + setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")} + defer func() { + setting.Service.EmailDomainAllowList = []glob.Glob{} + }() + + assert.NoError(t, AdminAddOrSetPrimaryEmailAddress(db.DefaultContext, user, "new-primary2@example2.com")) + + primary, err = user_model.GetPrimaryEmailAddressOfUser(db.DefaultContext, user.ID) + assert.NoError(t, err) + assert.Equal(t, "new-primary2@example2.com", primary.Email) + assert.Equal(t, user.Email, primary.Email) + + assert.NoError(t, AdminAddOrSetPrimaryEmailAddress(db.DefaultContext, user, "user27@example.com")) primary, err = user_model.GetPrimaryEmailAddressOfUser(db.DefaultContext, user.ID) assert.NoError(t, err) @@ -48,7 +62,7 @@ func TestAddOrSetPrimaryEmailAddress(t *testing.T) { emails, err = user_model.GetEmailAddresses(db.DefaultContext, user.ID) assert.NoError(t, err) - assert.Len(t, emails, 2) + assert.Len(t, emails, 3) } func TestReplacePrimaryEmailAddress(t *testing.T) { diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index 53bdd11afd..8a330a68e2 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -359,3 +359,32 @@ func TestAPICreateUser_NotAllowedEmailDomain(t *testing.T) { req = NewRequest(t, "DELETE", "/api/v1/admin/users/allowedUser1").AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) } + +func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")} + defer func() { + setting.Service.EmailDomainAllowList = []glob.Glob{} + }() + + adminUsername := "user1" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) + urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2") + + newEmail := "user2@example1.com" + req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ + LoginName: "user2", + SourceID: 0, + Email: &newEmail, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + originalEmail := "user2@example.com" + req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ + LoginName: "user2", + SourceID: 0, + Email: &originalEmail, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) +} From b058fb401271bf130ad193984042b6c6be06339a Mon Sep 17 00:00:00 2001 From: ChristopherHX <christopher.homberger@web.de> Date: Tue, 5 Mar 2024 18:34:42 +0100 Subject: [PATCH 18/62] Add ac claim for old docker/build-push-action@v3 / current buildx gha cache (#29584) Also resolves a warning for current releases ``` | ##[group]GitHub Actions runtime token ACs | ##[warning]Cannot parse GitHub Actions Runtime Token ACs: "undefined" is not valid JSON | ##[endgroup] ====> | ##[group]GitHub Actions runtime token ACs | ##[endgroup] ``` \* this is an error in v3 References in the docker org: - https://github.com/docker/build-push-action/blob/831ca179d3cf91cf0c90ca465a408fa61e2129a2/src/main.ts#L24 - https://github.com/docker/actions-toolkit/blob/7d8b4dc6694df35a06fae786427672ce27a8c18d/src/github.ts#L61 No known official action of GitHub makes use of this claim. Current releases throw an error when configure to use actions cache ``` | ERROR: failed to solve: failed to configure gha cache exporter: invalid token without access controls | ##[error]buildx failed with: ERROR: failed to solve: failed to configure gha cache exporter: invalid token without access controls ``` (cherry picked from commit 368743baf3d904f86b553a88718583906f571c87) --- services/actions/auth.go | 25 +++++++++++++++++++++++++ services/actions/auth_test.go | 9 +++++++++ 2 files changed, 34 insertions(+) diff --git a/services/actions/auth.go b/services/actions/auth.go index e0f9a9015d..8e934d89a8 100644 --- a/services/actions/auth.go +++ b/services/actions/auth.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -21,17 +22,41 @@ type actionsClaims struct { TaskID int64 RunID int64 JobID int64 + Ac string `json:"ac"` } +type actionsCacheScope struct { + Scope string + Permission actionsCachePermission +} + +type actionsCachePermission int + +const ( + actionsCachePermissionRead = 1 << iota + actionsCachePermissionWrite +) + func CreateAuthorizationToken(taskID, runID, jobID int64) (string, error) { now := time.Now() + ac, err := json.Marshal(&[]actionsCacheScope{ + { + Scope: "", + Permission: actionsCachePermissionWrite, + }, + }) + if err != nil { + return "", err + } + claims := actionsClaims{ RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(now.Add(24 * time.Hour)), NotBefore: jwt.NewNumericDate(now), }, Scp: fmt.Sprintf("Actions.Results:%d:%d", runID, jobID), + Ac: string(ac), TaskID: taskID, RunID: runID, JobID: jobID, diff --git a/services/actions/auth_test.go b/services/actions/auth_test.go index 1f62f17f52..f73ae8ae4c 100644 --- a/services/actions/auth_test.go +++ b/services/actions/auth_test.go @@ -7,6 +7,7 @@ import ( "net/http" "testing" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" "github.com/golang-jwt/jwt/v5" @@ -29,6 +30,14 @@ func TestCreateAuthorizationToken(t *testing.T) { taskIDClaim, ok := claims["TaskID"] assert.True(t, ok, "Has TaskID claim in jwt token") assert.Equal(t, float64(taskID), taskIDClaim, "Supplied taskid must match stored one") + acClaim, ok := claims["ac"] + assert.True(t, ok, "Has ac claim in jwt token") + ac, ok := acClaim.(string) + assert.True(t, ok, "ac claim is a string for buildx gha cache") + scopes := []actionsCacheScope{} + err = json.Unmarshal([]byte(ac), &scopes) + assert.NoError(t, err, "ac claim is a json list for buildx gha cache") + assert.GreaterOrEqual(t, len(scopes), 1, "Expected at least one action cache scope for buildx gha cache") } func TestParseAuthorizationToken(t *testing.T) { From 365fec3dc7c86eabefceb61303d1ebdbf27d8556 Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Wed, 6 Mar 2024 06:21:39 +0100 Subject: [PATCH 19/62] Update Twitter Logo (#29621) <img width="430" alt="image" src="https://github.com/go-gitea/gitea/assets/115237/9cf7b0a3-406b-4dd6-ab3d-d31a96b9335a"> (cherry picked from commit 8d32f3cb745124a99a8948ae771bcbb68fa3f297) --- public/assets/img/svg/gitea-twitter.svg | 2 +- web_src/svg/gitea-twitter.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/assets/img/svg/gitea-twitter.svg b/public/assets/img/svg/gitea-twitter.svg index 5d11c6eaec..5ed1e264ca 100644 --- a/public/assets/img/svg/gitea-twitter.svg +++ b/public/assets/img/svg/gitea-twitter.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" aria-hidden="true" class="gitea-twitter__svg gitea-twitter__gitea-twitter svg gitea-twitter" clip-rule="evenodd" viewBox="-89.009 -46.884 643.937 446.884" width="16" height="16"><path fill="#1da1f2" fill-rule="nonzero" d="M154.729 400c185.669 0 287.205-153.876 287.205-287.312 0-4.37-.089-8.72-.286-13.052A205.3 205.3 0 0 0 492 47.346c-18.087 8.044-37.55 13.458-57.968 15.899 20.841-12.501 36.84-32.278 44.389-55.852a202.4 202.4 0 0 1-64.098 24.511C395.903 12.276 369.679 0 340.641 0c-55.744 0-100.948 45.222-100.948 100.965 0 7.925.887 15.631 2.619 23.025-83.895-4.223-158.287-44.405-208.074-105.504A100.74 100.74 0 0 0 20.57 69.24c0 35.034 17.82 65.961 44.92 84.055a100.2 100.2 0 0 1-45.716-12.63c-.015.424-.015.837-.015 1.29 0 48.903 34.794 89.734 80.982 98.986a101 101 0 0 1-26.617 3.553c-6.493 0-12.821-.639-18.971-1.82 12.851 40.122 50.115 69.319 94.296 70.135-34.549 27.089-78.07 43.224-125.371 43.224A205 205 0 0 1 0 354.634c44.674 28.645 97.72 45.359 154.734 45.359"/></svg> \ No newline at end of file +<svg viewBox="0 0 24 24" class="svg gitea-twitter" xmlns="http://www.w3.org/2000/svg" width="16" height="16" aria-hidden="true"><path d="M14.095 10.316 22.286 1h-1.94L13.23 9.088 7.551 1H1l8.59 12.231L1 23h1.94l7.51-8.543 6 8.543H23zm-2.658 3.022-.872-1.218L3.64 2.432h2.98l5.59 7.821.869 1.219 7.265 10.166h-2.982z"/></svg> \ No newline at end of file diff --git a/web_src/svg/gitea-twitter.svg b/web_src/svg/gitea-twitter.svg index 096b9add2b..f972d23f90 100644 --- a/web_src/svg/gitea-twitter.svg +++ b/web_src/svg/gitea-twitter.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" clip-rule="evenodd" viewBox="-89.009 -46.884 643.937 446.884" class="svg gitea-twitter" width="16" height="16" aria-hidden="true"><path fill="#1da1f2" fill-rule="nonzero" d="M154.729 400c185.669 0 287.205-153.876 287.205-287.312 0-4.37-.089-8.72-.286-13.052A205.304 205.304 0 0 0 492 47.346c-18.087 8.044-37.55 13.458-57.968 15.899 20.841-12.501 36.84-32.278 44.389-55.852a202.42 202.42 0 0 1-64.098 24.511C395.903 12.276 369.679 0 340.641 0c-55.744 0-100.948 45.222-100.948 100.965 0 7.925.887 15.631 2.619 23.025-83.895-4.223-158.287-44.405-208.074-105.504A100.739 100.739 0 0 0 20.57 69.24c0 35.034 17.82 65.961 44.92 84.055a100.172 100.172 0 0 1-45.716-12.63c-.015.424-.015.837-.015 1.29 0 48.903 34.794 89.734 80.982 98.986a101.036 101.036 0 0 1-26.617 3.553c-6.493 0-12.821-.639-18.971-1.82 12.851 40.122 50.115 69.319 94.296 70.135-34.549 27.089-78.07 43.224-125.371 43.224A204.9 204.9 0 0 1 0 354.634c44.674 28.645 97.72 45.359 154.734 45.359"/></svg> \ No newline at end of file +<svg viewBox="0 0 24 24"><path d="M14.095 10.316 22.286 1h-1.94L13.23 9.088 7.551 1H1l8.59 12.231L1 23h1.94l7.51-8.543 6 8.543H23l-8.905-12.684zm-2.658 3.022-.872-1.218L3.64 2.432h2.98l5.59 7.821.869 1.219 7.265 10.166h-2.982l-5.926-8.3z"/></svg> \ No newline at end of file From 60aa763c51a37725788f541be127370bd6dc6b65 Mon Sep 17 00:00:00 2001 From: Earl Warren <contact@earl-warren.org> Date: Sun, 10 Mar 2024 15:51:19 +0700 Subject: [PATCH 20/62] templates: remove superfluous SanitizeHTML Introduced by Rename Str2html to SanitizeHTML and clarify its behavior (followup) --- templates/admin/dashboard.tmpl | 2 +- templates/home.tmpl | 8 ++++---- templates/mail/auth/activate.tmpl | 4 ++-- templates/mail/auth/activate_email.tmpl | 4 ++-- templates/mail/auth/register_notify.tmpl | 2 +- templates/mail/auth/reset_passwd.tmpl | 4 ++-- templates/mail/issue/default.tmpl | 2 +- templates/mail/notify/admin_new_user.tmpl | 4 ++-- templates/mail/team_invite.tmpl | 2 +- templates/org/settings/delete.tmpl | 2 +- templates/org/settings/labels.tmpl | 2 +- templates/org/team/invite.tmpl | 2 +- templates/org/team/new.tmpl | 4 ++-- templates/org/team/sidebar.tmpl | 10 +++++----- templates/repo/blame.tmpl | 4 ++-- templates/repo/branch/list.tmpl | 2 +- templates/repo/create.tmpl | 2 +- templates/repo/diff/box.tmpl | 2 +- templates/repo/diff/stats.tmpl | 2 +- templates/repo/empty.tmpl | 2 +- templates/repo/issue/labels/label_list.tmpl | 2 +- templates/repo/issue/view_content.tmpl | 2 +- templates/repo/migrate/options.tmpl | 2 +- templates/repo/release/list.tmpl | 2 +- templates/repo/settings/deploy_keys.tmpl | 2 +- templates/repo/settings/githooks.tmpl | 2 +- templates/repo/settings/options.tmpl | 2 +- templates/repo/settings/units/issues.tmpl | 4 ++-- templates/repo/settings/webhook/dingtalk.tmpl | 2 +- templates/repo/settings/webhook/discord.tmpl | 2 +- templates/repo/settings/webhook/feishu.tmpl | 4 ++-- templates/repo/settings/webhook/forgejo.tmpl | 2 +- templates/repo/settings/webhook/gitea.tmpl | 2 +- templates/repo/settings/webhook/gogs.tmpl | 2 +- templates/repo/settings/webhook/matrix.tmpl | 2 +- templates/repo/settings/webhook/msteams.tmpl | 2 +- templates/repo/settings/webhook/packagist.tmpl | 2 +- templates/repo/settings/webhook/settings.tmpl | 8 ++++---- templates/repo/settings/webhook/slack.tmpl | 2 +- templates/repo/settings/webhook/telegram.tmpl | 2 +- templates/repo/settings/webhook/wechatwork.tmpl | 2 +- templates/repo/unicode_escape_prompt.tmpl | 6 +++--- templates/status/500.tmpl | 2 +- templates/user/auth/finalize_openid.tmpl | 2 +- templates/user/auth/grant.tmpl | 4 ++-- templates/user/auth/reset_passwd.tmpl | 6 +++--- templates/user/auth/signin_inner.tmpl | 2 +- templates/user/auth/twofa.tmpl | 2 +- templates/user/settings/account.tmpl | 4 ++-- templates/user/settings/keys_gpg.tmpl | 2 +- templates/user/settings/keys_ssh.tmpl | 2 +- templates/user/settings/security/twofa.tmpl | 2 +- templates/user/settings/security/webauthn.tmpl | 2 +- 53 files changed, 77 insertions(+), 77 deletions(-) diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl index 53a12cbe27..cc7d338589 100644 --- a/templates/admin/dashboard.tmpl +++ b/templates/admin/dashboard.tmpl @@ -2,7 +2,7 @@ <div class="admin-setting-content"> {{if .NeedUpdate}} <div class="ui negative message flash-error"> - <p>{{(ctx.Locale.Tr "admin.dashboard.new_version_hint" .RemoteVersion AppVer) | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "admin.dashboard.new_version_hint" .RemoteVersion AppVer}}</p> </div> {{end}} <h4 class="ui top attached header"> diff --git a/templates/home.tmpl b/templates/home.tmpl index 393525dcd4..1e5369e7ee 100644 --- a/templates/home.tmpl +++ b/templates/home.tmpl @@ -17,7 +17,7 @@ {{svg "octicon-flame"}} {{ctx.Locale.Tr "startpage.install"}} </h1> <p class="large"> - {{ctx.Locale.Tr "startpage.install_desc" | SanitizeHTML}} + {{ctx.Locale.Tr "startpage.install_desc"}} </p> </div> <div class="eight wide center column"> @@ -25,7 +25,7 @@ {{svg "octicon-device-desktop"}} {{ctx.Locale.Tr "startpage.platform"}} </h1> <p class="large"> - {{ctx.Locale.Tr "startpage.platform_desc" | SanitizeHTML}} + {{ctx.Locale.Tr "startpage.platform_desc"}} </p> </div> </div> @@ -35,7 +35,7 @@ {{svg "octicon-rocket"}} {{ctx.Locale.Tr "startpage.lightweight"}} </h1> <p class="large"> - {{ctx.Locale.Tr "startpage.lightweight_desc" | SanitizeHTML}} + {{ctx.Locale.Tr "startpage.lightweight_desc"}} </p> </div> <div class="eight wide center column"> @@ -43,7 +43,7 @@ {{svg "octicon-code"}} {{ctx.Locale.Tr "startpage.license"}} </h1> <p class="large"> - {{ctx.Locale.Tr "startpage.license_desc" | SanitizeHTML}} + {{ctx.Locale.Tr "startpage.license_desc"}} </p> </div> </div> diff --git a/templates/mail/auth/activate.tmpl b/templates/mail/auth/activate.tmpl index c50717d315..b1bb4cb463 100644 --- a/templates/mail/auth/activate.tmpl +++ b/templates/mail/auth/activate.tmpl @@ -8,8 +8,8 @@ {{$activate_url := printf "%suser/activate?code=%s" AppUrl (QueryEscape .Code)}} <body> - <p>{{.locale.Tr "mail.activate_account.text_1" (.DisplayName|DotEscape) AppName | SanitizeHTML}}</p><br> - <p>{{.locale.Tr "mail.activate_account.text_2" .ActiveCodeLives | SanitizeHTML}}</p><p><a href="{{$activate_url}}">{{$activate_url}}</a></p><br> + <p>{{.locale.Tr "mail.activate_account.text_1" (.DisplayName|DotEscape) AppName}}</p><br> + <p>{{.locale.Tr "mail.activate_account.text_2" .ActiveCodeLives}}</p><p><a href="{{$activate_url}}">{{$activate_url}}</a></p><br> <p>{{.locale.Tr "mail.link_not_working_do_paste"}}</p> <p>© <a target="_blank" rel="noopener noreferrer" href="{{AppUrl}}">{{AppName}}</a></p> diff --git a/templates/mail/auth/activate_email.tmpl b/templates/mail/auth/activate_email.tmpl index 30fcb99ab8..3d32f80a4e 100644 --- a/templates/mail/auth/activate_email.tmpl +++ b/templates/mail/auth/activate_email.tmpl @@ -8,8 +8,8 @@ {{$activate_url := printf "%suser/activate_email?code=%s&email=%s" AppUrl (QueryEscape .Code) (QueryEscape .Email)}} <body> - <p>{{.locale.Tr "mail.hi_user_x" (.DisplayName|DotEscape) | SanitizeHTML}}</p><br> - <p>{{.locale.Tr "mail.activate_email.text" .ActiveCodeLives | SanitizeHTML}}</p><p><a href="{{$activate_url}}">{{$activate_url}}</a></p><br> + <p>{{.locale.Tr "mail.hi_user_x" (.DisplayName|DotEscape)}}</p><br> + <p>{{.locale.Tr "mail.activate_email.text" .ActiveCodeLives}}</p><p><a href="{{$activate_url}}">{{$activate_url}}</a></p><br> <p>{{.locale.Tr "mail.link_not_working_do_paste"}}</p> <p>© <a target="_blank" rel="noopener noreferrer" href="{{AppUrl}}">{{AppName}}</a></p> diff --git a/templates/mail/auth/register_notify.tmpl b/templates/mail/auth/register_notify.tmpl index 27c685e58f..62dbf7d927 100644 --- a/templates/mail/auth/register_notify.tmpl +++ b/templates/mail/auth/register_notify.tmpl @@ -8,7 +8,7 @@ {{$set_pwd_url := printf "%[1]suser/forgot_password" AppUrl}} <body> - <p>{{.locale.Tr "mail.hi_user_x" (.DisplayName|DotEscape) | SanitizeHTML}}</p><br> + <p>{{.locale.Tr "mail.hi_user_x" (.DisplayName|DotEscape)}}</p><br> <p>{{.locale.Tr "mail.register_notify.text_1" AppName}}</p><br> <p>{{.locale.Tr "mail.register_notify.text_2" .Username}}</p><p><a href="{{AppUrl}}user/login">{{AppUrl}}user/login</a></p><br> <p>{{.locale.Tr "mail.register_notify.text_3" $set_pwd_url}}</p><br> diff --git a/templates/mail/auth/reset_passwd.tmpl b/templates/mail/auth/reset_passwd.tmpl index e1af5b483c..55b1ecec3f 100644 --- a/templates/mail/auth/reset_passwd.tmpl +++ b/templates/mail/auth/reset_passwd.tmpl @@ -8,8 +8,8 @@ {{$recover_url := printf "%suser/recover_account?code=%s" AppUrl (QueryEscape .Code)}} <body> - <p>{{.locale.Tr "mail.hi_user_x" (.DisplayName|DotEscape) | SanitizeHTML}}</p><br> - <p>{{.locale.Tr "mail.reset_password.text" .ResetPwdCodeLives | SanitizeHTML}}</p><p><a href="{{$recover_url}}">{{$recover_url}}</a></p><br> + <p>{{.locale.Tr "mail.hi_user_x" (.DisplayName|DotEscape)}}</p><br> + <p>{{.locale.Tr "mail.reset_password.text" .ResetPwdCodeLives}}</p><p><a href="{{$recover_url}}">{{$recover_url}}</a></p><br> <p>{{.locale.Tr "mail.link_not_working_do_paste"}}</p> <p>© <a target="_blank" rel="noopener noreferrer" href="{{AppUrl}}">{{AppName}}</a></p> diff --git a/templates/mail/issue/default.tmpl b/templates/mail/issue/default.tmpl index 95a1555e87..4e83dbcfdb 100644 --- a/templates/mail/issue/default.tmpl +++ b/templates/mail/issue/default.tmpl @@ -16,7 +16,7 @@ </head> <body> - {{if .IsMention}}<p>{{.locale.Tr "mail.issue.x_mentioned_you" .Doer.Name | SanitizeHTML}}</p>{{end}} + {{if .IsMention}}<p>{{.locale.Tr "mail.issue.x_mentioned_you" .Doer.Name}}</p>{{end}} {{if eq .ActionName "push"}} <p> {{if .Comment.IsForcePush}} diff --git a/templates/mail/notify/admin_new_user.tmpl b/templates/mail/notify/admin_new_user.tmpl index 04f90b61ab..613c58194a 100644 --- a/templates/mail/notify/admin_new_user.tmpl +++ b/templates/mail/notify/admin_new_user.tmpl @@ -13,8 +13,8 @@ <body> <ul> - <h3>{{.Locale.Tr "mail.admin.new_user.user_info" | SanitizeHTML}}: <a href="{{.NewUserUrl}}">@{{.NewUser.Name}}</a></h3> - <li>{{.Locale.Tr "admin.users.created" | SanitizeHTML}}: {{DateTime "full" .NewUser.CreatedUnix}}</li> + <h3>{{.Locale.Tr "mail.admin.new_user.user_info"}}: <a href="{{.NewUserUrl}}">@{{.NewUser.Name}}</a></h3> + <li>{{.Locale.Tr "admin.users.created"}}: {{DateTime "full" .NewUser.CreatedUnix}}</li> </ul> <p> {{.Body | SanitizeHTML}} </p> </body> diff --git a/templates/mail/team_invite.tmpl b/templates/mail/team_invite.tmpl index 67b368f348..cb0c0c0a50 100644 --- a/templates/mail/team_invite.tmpl +++ b/templates/mail/team_invite.tmpl @@ -5,7 +5,7 @@ <meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no"> </head> <body> - <p>{{.locale.Tr "mail.team_invite.text_1" (DotEscape .Inviter.DisplayName) (DotEscape .Team.Name) (DotEscape .Organization.DisplayName) | SanitizeHTML}}</p> + <p>{{.locale.Tr "mail.team_invite.text_1" (DotEscape .Inviter.DisplayName) (DotEscape .Team.Name) (DotEscape .Organization.DisplayName)}}</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.team_invite.text_3" .Invite.Email}}</p> diff --git a/templates/org/settings/delete.tmpl b/templates/org/settings/delete.tmpl index 8c93e7548d..e1ef471e34 100644 --- a/templates/org/settings/delete.tmpl +++ b/templates/org/settings/delete.tmpl @@ -6,7 +6,7 @@ </h4> <div class="ui attached error segment"> <div class="ui red message"> - <p class="text left">{{svg "octicon-alert"}} {{ctx.Locale.Tr "org.settings.delete_prompt" | SanitizeHTML}}</p> + <p class="text left">{{svg "octicon-alert"}} {{ctx.Locale.Tr "org.settings.delete_prompt"}}</p> </div> <form class="ui form ignore-dirty" id="delete-form" action="{{.Link}}" method="post"> {{.CsrfTokenHtml}} diff --git a/templates/org/settings/labels.tmpl b/templates/org/settings/labels.tmpl index 56931def82..8eb7b4584e 100644 --- a/templates/org/settings/labels.tmpl +++ b/templates/org/settings/labels.tmpl @@ -2,7 +2,7 @@ <div class="org-setting-content"> <div class="gt-df gt-ac"> <div class="gt-f1"> - {{ctx.Locale.Tr "org.settings.labels_desc" | SanitizeHTML}} + {{ctx.Locale.Tr "org.settings.labels_desc"}} </div> <button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button> </div> diff --git a/templates/org/team/invite.tmpl b/templates/org/team/invite.tmpl index 5a8780c205..1167828d14 100644 --- a/templates/org/team/invite.tmpl +++ b/templates/org/team/invite.tmpl @@ -7,7 +7,7 @@ {{ctx.AvatarUtils.Avatar .Organization 140}} </div> <div class="content"> - <div class="header">{{ctx.Locale.Tr "org.teams.invite.title" .Team.Name .Organization.Name | SanitizeHTML}}</div> + <div class="header">{{ctx.Locale.Tr "org.teams.invite.title" .Team.Name .Organization.Name}}</div> <div class="meta">{{ctx.Locale.Tr "org.teams.invite.by" .Inviter.Name}}</div> <div class="description">{{ctx.Locale.Tr "org.teams.invite.description"}}</div> </div> diff --git a/templates/org/team/new.tmpl b/templates/org/team/new.tmpl index 99b16ee22d..50ef53b91b 100644 --- a/templates/org/team/new.tmpl +++ b/templates/org/team/new.tmpl @@ -32,14 +32,14 @@ <div class="ui radio checkbox"> <input type="radio" name="repo_access" value="specific" {{if not .Team.IncludesAllRepositories}}checked{{end}}> <label>{{ctx.Locale.Tr "org.teams.specific_repositories"}}</label> - <span class="help">{{ctx.Locale.Tr "org.teams.specific_repositories_helper" | SanitizeHTML}}</span> + <span class="help">{{ctx.Locale.Tr "org.teams.specific_repositories_helper"}}</span> </div> </div> <div class="field"> <div class="ui radio checkbox"> <input type="radio" name="repo_access" value="all" {{if .Team.IncludesAllRepositories}}checked{{end}}> <label>{{ctx.Locale.Tr "org.teams.all_repositories"}}</label> - <span class="help">{{ctx.Locale.Tr "org.teams.all_repositories_helper" | SanitizeHTML}}</span> + <span class="help">{{ctx.Locale.Tr "org.teams.all_repositories_helper"}}</span> </div> </div> diff --git a/templates/org/team/sidebar.tmpl b/templates/org/team/sidebar.tmpl index e59c0e5613..9311a46e38 100644 --- a/templates/org/team/sidebar.tmpl +++ b/templates/org/team/sidebar.tmpl @@ -27,16 +27,16 @@ </div> {{if eq .Team.LowerName "owners"}} <div class="item"> - {{ctx.Locale.Tr "org.teams.owners_permission_desc" | SanitizeHTML}} + {{ctx.Locale.Tr "org.teams.owners_permission_desc"}} </div> {{else}} <div class="item"> <h3>{{ctx.Locale.Tr "org.team_access_desc"}}</h3> <ul> {{if .Team.IncludesAllRepositories}} - <li>{{ctx.Locale.Tr "org.teams.all_repositories" | SanitizeHTML}}</li> + <li>{{ctx.Locale.Tr "org.teams.all_repositories"}}</li> {{else}} - <li>{{ctx.Locale.Tr "org.teams.specific_repositories" | SanitizeHTML}}</li> + <li>{{ctx.Locale.Tr "org.teams.specific_repositories"}}</li> {{end}} {{if .Team.CanCreateOrgRepo}} <li>{{ctx.Locale.Tr "org.teams.can_create_org_repo"}}</li> @@ -44,10 +44,10 @@ </ul> {{if (eq .Team.AccessMode 2)}} <h3>{{ctx.Locale.Tr "org.settings.permission"}}</h3> - {{ctx.Locale.Tr "org.teams.write_permission_desc" | SanitizeHTML}} + {{ctx.Locale.Tr "org.teams.write_permission_desc"}} {{else if (eq .Team.AccessMode 3)}} <h3>{{ctx.Locale.Tr "org.settings.permission"}}</h3> - {{ctx.Locale.Tr "org.teams.admin_permission_desc" | SanitizeHTML}} + {{ctx.Locale.Tr "org.teams.admin_permission_desc"}} {{else}} <table class="ui table"> <thead> diff --git a/templates/repo/blame.tmpl b/templates/repo/blame.tmpl index 75104cdcd2..6e0d0d1a5e 100644 --- a/templates/repo/blame.tmpl +++ b/templates/repo/blame.tmpl @@ -2,11 +2,11 @@ {{$revsFileLink := URLJoin .RepoLink "src" .BranchNameSubURL "/.git-blame-ignore-revs"}} {{if .UsesIgnoreRevs}} <div class="ui info message"> - <p>{{ctx.Locale.Tr "repo.blame.ignore_revs" $revsFileLink (print $revsFileLink "?bypass-blame-ignore=true") | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.blame.ignore_revs" $revsFileLink (print $revsFileLink "?bypass-blame-ignore=true")}}</p> </div> {{else}} <div class="ui error message"> - <p>{{ctx.Locale.Tr "repo.blame.ignore_revs.failed" $revsFileLink | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.blame.ignore_revs.failed" $revsFileLink}}</p> </div> {{end}} {{end}} diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl index 8f796a8b8f..916111faca 100644 --- a/templates/repo/branch/list.tmpl +++ b/templates/repo/branch/list.tmpl @@ -210,7 +210,7 @@ {{ctx.Locale.Tr "repo.branch.delete_html"}} <span class="name"></span> </div> <div class="content"> - <p>{{ctx.Locale.Tr "repo.branch.delete_desc" | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.branch.delete_desc"}}</p> </div> {{template "base/modal_actions_confirm" .}} </div> diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index 1ddf2c4808..73c9ca6a1f 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -158,7 +158,7 @@ {{end}} </div> </div> - <span class="help">{{ctx.Locale.Tr "repo.license_helper_desc" "https://choosealicense.com/" | SanitizeHTML}}</span> + <span class="help">{{ctx.Locale.Tr "repo.license_helper_desc" "https://choosealicense.com/"}}</span> </div> <div class="inline field"> diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 40c163c555..886004ea65 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -19,7 +19,7 @@ {{end}} {{if not .DiffNotAvailable}} <div class="diff-detail-stats gt-df gt-ac gt-fw"> - {{svg "octicon-diff" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion | SanitizeHTML}} + {{svg "octicon-diff" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion}} </div> {{end}} </div> diff --git a/templates/repo/diff/stats.tmpl b/templates/repo/diff/stats.tmpl index 64ee17a7c5..b7acb3d49b 100644 --- a/templates/repo/diff/stats.tmpl +++ b/templates/repo/diff/stats.tmpl @@ -1,5 +1,5 @@ {{Eval .file.Addition "+" .file.Deletion}} -<span class="diff-stats-bar gt-mx-3" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.stats_desc_file" (Eval .file.Addition "+" .file.Deletion) .file.Addition .file.Deletion | SanitizeHTML}}"> +<span class="diff-stats-bar gt-mx-3" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.stats_desc_file" (Eval .file.Addition "+" .file.Deletion) .file.Addition .file.Deletion}}"> {{/* if the denominator is zero, then the float result is "width: NaNpx", as before, it just works */}} <div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .file.Addition "/" "(" .file.Addition "+" .file.Deletion "+" 0.0 ")"}}%"></div> </span> diff --git a/templates/repo/empty.tmpl b/templates/repo/empty.tmpl index a3adc5d68c..a858a728e9 100644 --- a/templates/repo/empty.tmpl +++ b/templates/repo/empty.tmpl @@ -24,7 +24,7 @@ </h4> <div class="ui attached guide table segment empty-repo-guide"> <div class="item"> - <h3>{{ctx.Locale.Tr "repo.clone_this_repo"}} <small>{{ctx.Locale.Tr "repo.clone_helper" "http://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository" | SanitizeHTML}}</small></h3> + <h3>{{ctx.Locale.Tr "repo.clone_this_repo"}} <small>{{ctx.Locale.Tr "repo.clone_helper" "http://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository"}}</small></h3> <div class="repo-button-row"> {{if and .CanWriteCode (not .Repository.IsArchived)}} diff --git a/templates/repo/issue/labels/label_list.tmpl b/templates/repo/issue/labels/label_list.tmpl index 252b5816b3..9b0061b60e 100644 --- a/templates/repo/issue/labels/label_list.tmpl +++ b/templates/repo/issue/labels/label_list.tmpl @@ -61,7 +61,7 @@ <li class="item"> <div class="ui grid middle aligned"> <div class="ten wide column"> - {{ctx.Locale.Tr "repo.org_labels_desc" | SanitizeHTML}} + {{ctx.Locale.Tr "repo.org_labels_desc"}} {{if .IsOrganizationOwner}} <a href="{{.OrganizationLink}}/settings/labels">({{ctx.Locale.Tr "repo.org_labels_desc_manage"}})</a>: {{end}} diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index 980b4f373c..7e6816613d 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -181,7 +181,7 @@ {{ctx.Locale.Tr "repo.branch.delete" .HeadTarget}} </div> <div class="content"> - <p>{{ctx.Locale.Tr "repo.branch.delete_desc" | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.branch.delete_desc"}}</p> </div> {{template "base/modal_actions_confirm" .}} </div> diff --git a/templates/repo/migrate/options.tmpl b/templates/repo/migrate/options.tmpl index d0235a0f85..1cf8600749 100644 --- a/templates/repo/migrate/options.tmpl +++ b/templates/repo/migrate/options.tmpl @@ -17,7 +17,7 @@ <span id="lfs_settings" class="gt-hidden">(<a id="lfs_settings_show" href="#">{{ctx.Locale.Tr "repo.settings.advanced_settings"}}</a>)</span> </div> <div id="lfs_endpoint" class="gt-hidden"> - <span class="help">{{ctx.Locale.Tr "repo.migrate_options_lfs_endpoint.description" "https://github.com/git-lfs/git-lfs/blob/main/docs/api/server-discovery.md#server-discovery" | SanitizeHTML}}{{if .ContextUser.CanImportLocal}} {{ctx.Locale.Tr "repo.migrate_options_lfs_endpoint.description.local"}}{{end}}</span> + <span class="help">{{ctx.Locale.Tr "repo.migrate_options_lfs_endpoint.description" "https://github.com/git-lfs/git-lfs/blob/main/docs/api/server-discovery.md#server-discovery"}}{{if .ContextUser.CanImportLocal}} {{ctx.Locale.Tr "repo.migrate_options_lfs_endpoint.description.local"}}{{end}}</span> <div class="inline field {{if .Err_LFSEndpoint}}error{{end}}"> <label>{{ctx.Locale.Tr "repo.migrate_options_lfs_endpoint.label"}}</label> <input name="lfs_endpoint" value="{{.lfs_endpoint}}" placeholder="{{ctx.Locale.Tr "repo.migrate_options_lfs_endpoint.placeholder"}}"> diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index 541af86ff5..f1934493d8 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -54,7 +54,7 @@ <span class="time">{{TimeSinceUnix $release.CreatedUnix ctx.Locale}}</span> {{end}} {{if and (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}} - | <span class="ahead"><a href="{{$.RepoLink}}/compare/{{$release.TagName | PathEscapeSegments}}...{{$release.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" $release.NumCommitsBehind | SanitizeHTML}}</a> {{ctx.Locale.Tr "repo.release.ahead.target" $release.TargetBehind}}</span> + | <span class="ahead"><a href="{{$.RepoLink}}/compare/{{$release.TagName | PathEscapeSegments}}...{{$release.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" $release.NumCommitsBehind}}</a> {{ctx.Locale.Tr "repo.release.ahead.target" $release.TargetBehind}}</span> {{end}} </p> <div class="markup desc"> diff --git a/templates/repo/settings/deploy_keys.tmpl b/templates/repo/settings/deploy_keys.tmpl index 0b0f56520b..a79a196825 100644 --- a/templates/repo/settings/deploy_keys.tmpl +++ b/templates/repo/settings/deploy_keys.tmpl @@ -31,7 +31,7 @@ <label for="is_writable"> {{ctx.Locale.Tr "repo.settings.is_writable"}} </label> - <small style="padding-left: 26px;">{{ctx.Locale.Tr "repo.settings.is_writable_info" | SanitizeHTML}}</small> + <small style="padding-left: 26px;">{{ctx.Locale.Tr "repo.settings.is_writable_info"}}</small> </div> </div> <button class="ui primary button"> diff --git a/templates/repo/settings/githooks.tmpl b/templates/repo/settings/githooks.tmpl index 294e545944..3fce29d545 100644 --- a/templates/repo/settings/githooks.tmpl +++ b/templates/repo/settings/githooks.tmpl @@ -6,7 +6,7 @@ <div class="ui attached segment"> <div class="ui list"> <div class="item"> - {{ctx.Locale.Tr "repo.settings.githooks_desc" | SanitizeHTML}} + {{ctx.Locale.Tr "repo.settings.githooks_desc"}} </div> {{range .Hooks}} <div class="item truncated-item-container"> diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 31345ca4ca..99d75b8a84 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -191,7 +191,7 @@ <div class="field {{if .Err_LFSEndpoint}}error{{end}}"> <label for="mirror_lfs_endpoint">{{ctx.Locale.Tr "repo.mirror_lfs_endpoint"}}</label> <input id="mirror_lfs_endpoint" name="mirror_lfs_endpoint" value="{{.PullMirror.LFSEndpoint}}" placeholder="{{ctx.Locale.Tr "repo.migrate_options_lfs_endpoint.placeholder"}}"> - <p class="help">{{ctx.Locale.Tr "repo.mirror_lfs_endpoint_desc" "https://github.com/git-lfs/git-lfs/blob/main/docs/api/server-discovery.md#server-discovery" | SanitizeHTML}}</p> + <p class="help">{{ctx.Locale.Tr "repo.mirror_lfs_endpoint_desc" "https://github.com/git-lfs/git-lfs/blob/main/docs/api/server-discovery.md#server-discovery"}}</p> </div> {{end}} <div class="field"> diff --git a/templates/repo/settings/units/issues.tmpl b/templates/repo/settings/units/issues.tmpl index 7ddafd1c6a..77f5782151 100644 --- a/templates/repo/settings/units/issues.tmpl +++ b/templates/repo/settings/units/issues.tmpl @@ -61,7 +61,7 @@ <div class="field"> <label for="tracker_url_format">{{ctx.Locale.Tr "repo.settings.tracker_url_format"}}</label> <input id="tracker_url_format" name="tracker_url_format" type="url" value="{{(.Repository.MustGetUnit $.Context $.UnitTypeExternalTracker).ExternalTrackerConfig.ExternalTrackerFormat}}" placeholder="https://github.com/{user}/{repo}/issues/{index}"> - <p class="help">{{ctx.Locale.Tr "repo.settings.tracker_url_format_desc" | SanitizeHTML}}</p> + <p class="help">{{ctx.Locale.Tr "repo.settings.tracker_url_format_desc"}}</p> </div> <div class="inline fields"> <label for="issue_style">{{ctx.Locale.Tr "repo.settings.tracker_issue_style"}}</label> @@ -89,7 +89,7 @@ <div class="field {{if ne $externalTrackerStyle "regexp"}}disabled{{end}}" id="tracker-issue-style-regex-box"> <label for="external_tracker_regexp_pattern">{{ctx.Locale.Tr "repo.settings.tracker_issue_style.regexp_pattern"}}</label> <input id="external_tracker_regexp_pattern" name="external_tracker_regexp_pattern" value="{{(.Repository.MustGetUnit $.Context $.UnitTypeExternalTracker).ExternalTrackerConfig.ExternalTrackerRegexpPattern}}"> - <p class="help">{{ctx.Locale.Tr "repo.settings.tracker_issue_style.regexp_pattern_desc" | SanitizeHTML}}</p> + <p class="help">{{ctx.Locale.Tr "repo.settings.tracker_issue_style.regexp_pattern_desc"}}</p> </div> </div> </div> diff --git a/templates/repo/settings/webhook/dingtalk.tmpl b/templates/repo/settings/webhook/dingtalk.tmpl index a620d9e241..0ba99e98ee 100644 --- a/templates/repo/settings/webhook/dingtalk.tmpl +++ b/templates/repo/settings/webhook/dingtalk.tmpl @@ -1,5 +1,5 @@ {{if eq .HookType "dingtalk"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://dingtalk.com" (ctx.Locale.Tr "repo.settings.web_hook_name_dingtalk") | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://dingtalk.com" (ctx.Locale.Tr "repo.settings.web_hook_name_dingtalk")}}</p> <form class="ui form" action="{{.BaseLink}}/dingtalk/{{or .Webhook.ID "new"}}" method="post"> {{.CsrfTokenHtml}} <div class="required field {{if .Err_PayloadURL}}error{{end}}"> diff --git a/templates/repo/settings/webhook/discord.tmpl b/templates/repo/settings/webhook/discord.tmpl index ff1ca75d3f..b623a6d8d3 100644 --- a/templates/repo/settings/webhook/discord.tmpl +++ b/templates/repo/settings/webhook/discord.tmpl @@ -1,5 +1,5 @@ {{if eq .HookType "discord"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://discord.com" (ctx.Locale.Tr "repo.settings.web_hook_name_discord") | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://discord.com" (ctx.Locale.Tr "repo.settings.web_hook_name_discord")}}</p> <form class="ui form" action="{{.BaseLink}}/discord/{{or .Webhook.ID "new"}}" method="post"> {{.CsrfTokenHtml}} <div class="required field {{if .Err_PayloadURL}}error{{end}}"> diff --git a/templates/repo/settings/webhook/feishu.tmpl b/templates/repo/settings/webhook/feishu.tmpl index 1acb42c76c..d80deab26f 100644 --- a/templates/repo/settings/webhook/feishu.tmpl +++ b/templates/repo/settings/webhook/feishu.tmpl @@ -1,6 +1,6 @@ {{if eq .HookType "feishu"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://feishu.cn" (ctx.Locale.Tr "repo.settings.web_hook_name_feishu") | SanitizeHTML}}</p> - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://larksuite.com" (ctx.Locale.Tr "repo.settings.web_hook_name_larksuite") | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://feishu.cn" (ctx.Locale.Tr "repo.settings.web_hook_name_feishu")}}</p> + <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://larksuite.com" (ctx.Locale.Tr "repo.settings.web_hook_name_larksuite")}}</p> <form class="ui form" action="{{.BaseLink}}/feishu/{{or .Webhook.ID "new"}}" method="post"> {{.CsrfTokenHtml}} <div class="required field {{if .Err_PayloadURL}}error{{end}}"> diff --git a/templates/repo/settings/webhook/forgejo.tmpl b/templates/repo/settings/webhook/forgejo.tmpl index 0045f27616..0bfb99115c 100644 --- a/templates/repo/settings/webhook/forgejo.tmpl +++ b/templates/repo/settings/webhook/forgejo.tmpl @@ -1,5 +1,5 @@ {{if eq .HookType "forgejo"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://forgejo.org/docs/latest/user/webhooks/" (ctx.Locale.Tr "repo.settings.web_hook_name_forgejo") | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://forgejo.org/docs/latest/user/webhooks/" (ctx.Locale.Tr "repo.settings.web_hook_name_forgejo")}}</p> <form class="ui form" action="{{.BaseLink}}/forgejo/{{or .Webhook.ID "new"}}" method="post"> {{template "base/disable_form_autofill"}} {{.CsrfTokenHtml}} diff --git a/templates/repo/settings/webhook/gitea.tmpl b/templates/repo/settings/webhook/gitea.tmpl index cb2e789459..38ec29c784 100644 --- a/templates/repo/settings/webhook/gitea.tmpl +++ b/templates/repo/settings/webhook/gitea.tmpl @@ -1,5 +1,5 @@ {{if eq .HookType "gitea"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://forgejo.org/docs/latest/user/webhooks/" (ctx.Locale.Tr "repo.settings.web_hook_name_gitea") | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://forgejo.org/docs/latest/user/webhooks/" (ctx.Locale.Tr "repo.settings.web_hook_name_gitea")}}</p> <form class="ui form" action="{{.BaseLink}}/gitea/{{or .Webhook.ID "new"}}" method="post"> {{template "base/disable_form_autofill"}} {{.CsrfTokenHtml}} diff --git a/templates/repo/settings/webhook/gogs.tmpl b/templates/repo/settings/webhook/gogs.tmpl index a81b15fd5b..ff1742aa18 100644 --- a/templates/repo/settings/webhook/gogs.tmpl +++ b/templates/repo/settings/webhook/gogs.tmpl @@ -1,5 +1,5 @@ {{if eq .HookType "gogs"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://forgejo.org/docs/latest/user/webhooks/" (ctx.Locale.Tr "repo.settings.web_hook_name_gogs") | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://forgejo.org/docs/latest/user/webhooks/" (ctx.Locale.Tr "repo.settings.web_hook_name_gogs")}}</p> <form class="ui form" action="{{.BaseLink}}/gogs/{{or .Webhook.ID "new"}}" method="post"> {{template "base/disable_form_autofill"}} {{.CsrfTokenHtml}} diff --git a/templates/repo/settings/webhook/matrix.tmpl b/templates/repo/settings/webhook/matrix.tmpl index 662beeaccb..7f1c9f08e6 100644 --- a/templates/repo/settings/webhook/matrix.tmpl +++ b/templates/repo/settings/webhook/matrix.tmpl @@ -1,5 +1,5 @@ {{if eq .HookType "matrix"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://matrix.org/" (ctx.Locale.Tr "repo.settings.web_hook_name_matrix") | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://matrix.org/" (ctx.Locale.Tr "repo.settings.web_hook_name_matrix")}}</p> <form class="ui form" action="{{.BaseLink}}/matrix/{{or .Webhook.ID "new"}}" method="post"> {{.CsrfTokenHtml}} <div class="required field {{if .Err_HomeserverURL}}error{{end}}"> diff --git a/templates/repo/settings/webhook/msteams.tmpl b/templates/repo/settings/webhook/msteams.tmpl index 08e381de38..62ea24e763 100644 --- a/templates/repo/settings/webhook/msteams.tmpl +++ b/templates/repo/settings/webhook/msteams.tmpl @@ -1,5 +1,5 @@ {{if eq .HookType "msteams"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://teams.microsoft.com" (ctx.Locale.Tr "repo.settings.web_hook_name_msteams") | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://teams.microsoft.com" (ctx.Locale.Tr "repo.settings.web_hook_name_msteams")}}</p> <form class="ui form" action="{{.BaseLink}}/msteams/{{or .Webhook.ID "new"}}" method="post"> {{.CsrfTokenHtml}} <div class="required field {{if .Err_PayloadURL}}error{{end}}"> diff --git a/templates/repo/settings/webhook/packagist.tmpl b/templates/repo/settings/webhook/packagist.tmpl index 48a4ecce6d..5330daf339 100644 --- a/templates/repo/settings/webhook/packagist.tmpl +++ b/templates/repo/settings/webhook/packagist.tmpl @@ -1,5 +1,5 @@ {{if eq .HookType "packagist"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://packagist.org" (ctx.Locale.Tr "repo.settings.web_hook_name_packagist") | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://packagist.org" (ctx.Locale.Tr "repo.settings.web_hook_name_packagist")}}</p> <form class="ui form" action="{{.BaseLink}}/packagist/{{or .Webhook.ID "new"}}" method="post"> {{.CsrfTokenHtml}} <div class="required field {{if .Err_Username}}error{{end}}"> diff --git a/templates/repo/settings/webhook/settings.tmpl b/templates/repo/settings/webhook/settings.tmpl index a3764371e2..3ef8894444 100644 --- a/templates/repo/settings/webhook/settings.tmpl +++ b/templates/repo/settings/webhook/settings.tmpl @@ -5,19 +5,19 @@ <div class="field"> <div class="ui radio non-events checkbox"> <input name="events" type="radio" value="push_only" {{if or $isNew .Webhook.PushOnly}}checked{{end}}> - <label>{{ctx.Locale.Tr "repo.settings.event_push_only" | SanitizeHTML}}</label> + <label>{{ctx.Locale.Tr "repo.settings.event_push_only"}}</label> </div> </div> <div class="field"> <div class="ui radio non-events checkbox"> <input name="events" type="radio" value="send_everything" {{if .Webhook.SendEverything}}checked{{end}}> - <label>{{ctx.Locale.Tr "repo.settings.event_send_everything" | SanitizeHTML}}</label> + <label>{{ctx.Locale.Tr "repo.settings.event_send_everything"}}</label> </div> </div> <div class="field"> <div class="ui radio events checkbox"> <input name="events" type="radio" value="choose_events" {{if .Webhook.ChooseEvents}}checked{{end}}> - <label>{{ctx.Locale.Tr "repo.settings.event_choose" | SanitizeHTML}}</label> + <label>{{ctx.Locale.Tr "repo.settings.event_choose"}}</label> </div> </div> </div> @@ -255,7 +255,7 @@ <div class="field"> <label for="branch_filter">{{ctx.Locale.Tr "repo.settings.branch_filter"}}</label> <input id="branch_filter" name="branch_filter" type="text" value="{{or .Webhook.BranchFilter "*"}}"> - <span class="help">{{ctx.Locale.Tr "repo.settings.branch_filter_desc" | SanitizeHTML}}</span> + <span class="help">{{ctx.Locale.Tr "repo.settings.branch_filter_desc"}}</span> </div> <!-- Authorization Header --> diff --git a/templates/repo/settings/webhook/slack.tmpl b/templates/repo/settings/webhook/slack.tmpl index 5dfc7a67b7..78fc25b9f4 100644 --- a/templates/repo/settings/webhook/slack.tmpl +++ b/templates/repo/settings/webhook/slack.tmpl @@ -1,5 +1,5 @@ {{if eq .HookType "slack"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://slack.com" (ctx.Locale.Tr "repo.settings.web_hook_name_slack") | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://slack.com" (ctx.Locale.Tr "repo.settings.web_hook_name_slack")}}</p> <form class="ui form" action="{{.BaseLink}}/slack/{{or .Webhook.ID "new"}}" method="post"> {{.CsrfTokenHtml}} <div class="required field {{if .Err_PayloadURL}}error{{end}}"> diff --git a/templates/repo/settings/webhook/telegram.tmpl b/templates/repo/settings/webhook/telegram.tmpl index 62cac464b6..f92c2be0db 100644 --- a/templates/repo/settings/webhook/telegram.tmpl +++ b/templates/repo/settings/webhook/telegram.tmpl @@ -1,5 +1,5 @@ {{if eq .HookType "telegram"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://core.telegram.org/bots" (ctx.Locale.Tr "repo.settings.web_hook_name_telegram") | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://core.telegram.org/bots" (ctx.Locale.Tr "repo.settings.web_hook_name_telegram")}}</p> <form class="ui form" action="{{.BaseLink}}/telegram/{{or .Webhook.ID "new"}}" method="post"> {{.CsrfTokenHtml}} <div class="required field {{if .Err_BotToken}}error{{end}}"> diff --git a/templates/repo/settings/webhook/wechatwork.tmpl b/templates/repo/settings/webhook/wechatwork.tmpl index 95c4cefe64..78a1617123 100644 --- a/templates/repo/settings/webhook/wechatwork.tmpl +++ b/templates/repo/settings/webhook/wechatwork.tmpl @@ -1,5 +1,5 @@ {{if eq .HookType "wechatwork"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://work.weixin.qq.com" (ctx.Locale.Tr "repo.settings.web_hook_name_wechatwork") | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://work.weixin.qq.com" (ctx.Locale.Tr "repo.settings.web_hook_name_wechatwork")}}</p> <form class="ui form" action="{{.BaseLink}}/wechatwork/{{or .Webhook.ID "new"}}" method="post"> {{.CsrfTokenHtml}} <div class="required field {{if .Err_PayloadURL}}error{{end}}"> diff --git a/templates/repo/unicode_escape_prompt.tmpl b/templates/repo/unicode_escape_prompt.tmpl index ecc9c33d83..8bceafa8bb 100644 --- a/templates/repo/unicode_escape_prompt.tmpl +++ b/templates/repo/unicode_escape_prompt.tmpl @@ -5,9 +5,9 @@ <div class="header"> {{ctx.Locale.Tr "repo.invisible_runes_header"}} </div> - <p>{{ctx.Locale.Tr "repo.invisible_runes_description" | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.invisible_runes_description"}}</p> {{if .EscapeStatus.HasAmbiguous}} - <p>{{ctx.Locale.Tr "repo.ambiguous_runes_description" | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.ambiguous_runes_description"}}</p> {{end}} </div> {{else if .EscapeStatus.HasAmbiguous}} @@ -16,7 +16,7 @@ <div class="header"> {{ctx.Locale.Tr "repo.ambiguous_runes_header"}} </div> - <p>{{ctx.Locale.Tr "repo.ambiguous_runes_description" | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "repo.ambiguous_runes_description"}}</p> </div> {{end}} {{end}} diff --git a/templates/status/500.tmpl b/templates/status/500.tmpl index 3825d9de4d..98e3044db2 100644 --- a/templates/status/500.tmpl +++ b/templates/status/500.tmpl @@ -45,7 +45,7 @@ {{end}} <div class="center gt-mt-5"> {{if or .SignedUser.IsAdmin .ShowFooterVersion}}<p>{{ctx.Locale.Tr "admin.config.app_ver"}}: {{AppVer}}</p>{{end}} - {{if .SignedUser.IsAdmin}}<p>{{ctx.Locale.Tr "error.report_message" | SanitizeHTML}}</p>{{end}} + {{if .SignedUser.IsAdmin}}<p>{{ctx.Locale.Tr "error.report_message"}}</p>{{end}} </div> </div> </div> diff --git a/templates/user/auth/finalize_openid.tmpl b/templates/user/auth/finalize_openid.tmpl index f7da41d16c..1c1dcdb825 100644 --- a/templates/user/auth/finalize_openid.tmpl +++ b/templates/user/auth/finalize_openid.tmpl @@ -35,7 +35,7 @@ {{if .ShowRegistrationButton}} <div class="inline field"> <label></label> - <a href="{{AppSubUrl}}/user/sign_up">{{ctx.Locale.Tr "auth.sign_up_now" | SanitizeHTML}}</a> + <a href="{{AppSubUrl}}/user/sign_up">{{ctx.Locale.Tr "auth.sign_up_now"}}</a> </div> {{end}} </form> diff --git a/templates/user/auth/grant.tmpl b/templates/user/auth/grant.tmpl index d56692ccce..cb9bba8749 100644 --- a/templates/user/auth/grant.tmpl +++ b/templates/user/auth/grant.tmpl @@ -9,11 +9,11 @@ {{template "base/alert" .}} <p> <b>{{ctx.Locale.Tr "auth.authorize_application_description"}}</b><br> - {{ctx.Locale.Tr "auth.authorize_application_created_by" .ApplicationCreatorLinkHTML | SanitizeHTML}} + {{ctx.Locale.Tr "auth.authorize_application_created_by" .ApplicationCreatorLinkHTML}} </p> </div> <div class="ui attached segment"> - <p>{{ctx.Locale.Tr "auth.authorize_redirect_notice" .ApplicationRedirectDomainHTML | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "auth.authorize_redirect_notice" .ApplicationRedirectDomainHTML}}</p> </div> <div class="ui attached segment"> <form method="post" action="{{AppSubUrl}}/login/oauth/grant"> diff --git a/templates/user/auth/reset_passwd.tmpl b/templates/user/auth/reset_passwd.tmpl index c0bf693f46..9fee30f554 100644 --- a/templates/user/auth/reset_passwd.tmpl +++ b/templates/user/auth/reset_passwd.tmpl @@ -34,7 +34,7 @@ <h4 class="ui dividing header"> {{ctx.Locale.Tr "twofa"}} </h4> - <div class="ui warning visible message">{{ctx.Locale.Tr "settings.twofa_is_enrolled" | SanitizeHTML}}</div> + <div class="ui warning visible message">{{ctx.Locale.Tr "settings.twofa_is_enrolled"}}</div> {{if .scratch_code}} <div class="required inline field {{if .Err_Token}}error{{end}}"> <label for="token">{{ctx.Locale.Tr "auth.scratch_code"}}</label> @@ -53,11 +53,11 @@ <label></label> <button class="ui primary button">{{ctx.Locale.Tr "auth.reset_password_helper"}}</button> {{if and .has_two_factor (not .scratch_code)}} - <a href="{{.Link}}?code={{.Code}}&scratch_code=true">{{ctx.Locale.Tr "auth.use_scratch_code" | SanitizeHTML}}</a> + <a href="{{.Link}}?code={{.Code}}&scratch_code=true">{{ctx.Locale.Tr "auth.use_scratch_code"}}</a> {{end}} </div> {{else}} - <p class="center">{{ctx.Locale.Tr "auth.invalid_code_forgot_password" (printf "%s/user/forgot_password" AppSubUrl) | SanitizeHTML}}</p> + <p class="center">{{ctx.Locale.Tr "auth.invalid_code_forgot_password" (printf "%s/user/forgot_password" AppSubUrl)}}</p> {{end}} </div> </form> diff --git a/templates/user/auth/signin_inner.tmpl b/templates/user/auth/signin_inner.tmpl index 15733eb9bf..0d0064b02a 100644 --- a/templates/user/auth/signin_inner.tmpl +++ b/templates/user/auth/signin_inner.tmpl @@ -48,7 +48,7 @@ {{if .ShowRegistrationButton}} <div class="inline field"> <label></label> - <a href="{{AppSubUrl}}/user/sign_up">{{ctx.Locale.Tr "auth.sign_up_now" | SanitizeHTML}}</a> + <a href="{{AppSubUrl}}/user/sign_up">{{ctx.Locale.Tr "auth.sign_up_now"}}</a> </div> {{end}} diff --git a/templates/user/auth/twofa.tmpl b/templates/user/auth/twofa.tmpl index 8a898c7dd8..5260178d13 100644 --- a/templates/user/auth/twofa.tmpl +++ b/templates/user/auth/twofa.tmpl @@ -17,7 +17,7 @@ <div class="inline field"> <label></label> <button class="ui primary button">{{ctx.Locale.Tr "auth.verify"}}</button> - <a href="{{AppSubUrl}}/user/two_factor/scratch">{{ctx.Locale.Tr "auth.use_scratch_code" | SanitizeHTML}}</a> + <a href="{{AppSubUrl}}/user/two_factor/scratch">{{ctx.Locale.Tr "auth.use_scratch_code"}}</a> </div> </div> </form> diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl index b9dc3a9ce4..29441f97ee 100644 --- a/templates/user/settings/account.tmpl +++ b/templates/user/settings/account.tmpl @@ -134,9 +134,9 @@ </h4> <div class="ui attached error segment"> <div class="ui red message"> - <p class="text left">{{svg "octicon-alert"}} {{ctx.Locale.Tr "settings.delete_prompt" | SanitizeHTML}}</p> + <p class="text left">{{svg "octicon-alert"}} {{ctx.Locale.Tr "settings.delete_prompt"}}</p> {{if .UserDeleteWithComments}} - <p class="text left gt-font-semibold">{{ctx.Locale.Tr "settings.delete_with_all_comments" .UserDeleteWithCommentsMaxTime | SanitizeHTML}}</p> + <p class="text left gt-font-semibold">{{ctx.Locale.Tr "settings.delete_with_all_comments" .UserDeleteWithCommentsMaxTime}}</p> {{end}} </div> <form class="ui form ignore-dirty" id="delete-form" action="{{AppSubUrl}}/user/settings/account/delete" method="post"> diff --git a/templates/user/settings/keys_gpg.tmpl b/templates/user/settings/keys_gpg.tmpl index 3ab066c954..293686ca96 100644 --- a/templates/user/settings/keys_gpg.tmpl +++ b/templates/user/settings/keys_gpg.tmpl @@ -43,7 +43,7 @@ <div class="flex-item"> <p> {{ctx.Locale.Tr "settings.gpg_desc"}}<br> - {{ctx.Locale.Tr "settings.gpg_helper" "https://docs.codeberg.org/security/gpg-key/" | SanitizeHTML}} + {{ctx.Locale.Tr "settings.gpg_helper" "https://docs.codeberg.org/security/gpg-key/"}} </p> </div> {{range .GPGKeys}} diff --git a/templates/user/settings/keys_ssh.tmpl b/templates/user/settings/keys_ssh.tmpl index 9045b6c27c..6aa803a418 100644 --- a/templates/user/settings/keys_ssh.tmpl +++ b/templates/user/settings/keys_ssh.tmpl @@ -31,7 +31,7 @@ <div class="flex-item"> <p> {{ctx.Locale.Tr "settings.ssh_desc"}}<br> - {{ctx.Locale.Tr "settings.ssh_helper" "https://docs.codeberg.org/security/ssh-key/" "https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/troubleshooting-ssh" | SanitizeHTML}} + {{ctx.Locale.Tr "settings.ssh_helper" "https://docs.codeberg.org/security/ssh-key/" "https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/troubleshooting-ssh"}} </p> </div> {{if .DisableSSH}} diff --git a/templates/user/settings/security/twofa.tmpl b/templates/user/settings/security/twofa.tmpl index 87cc90e477..adebce4265 100644 --- a/templates/user/settings/security/twofa.tmpl +++ b/templates/user/settings/security/twofa.tmpl @@ -4,7 +4,7 @@ <div class="ui attached segment"> <p>{{ctx.Locale.Tr "settings.twofa_desc"}}</p> {{if .TOTPEnrolled}} - <p>{{ctx.Locale.Tr "settings.twofa_is_enrolled" | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "settings.twofa_is_enrolled"}}</p> <form class="ui form" action="{{AppSubUrl}}/user/settings/security/two_factor/regenerate_scratch" method="post" enctype="multipart/form-data"> {{.CsrfTokenHtml}} <p>{{ctx.Locale.Tr "settings.regenerate_scratch_token_desc"}}</p> diff --git a/templates/user/settings/security/webauthn.tmpl b/templates/user/settings/security/webauthn.tmpl index a7fa04a521..eceee191bd 100644 --- a/templates/user/settings/security/webauthn.tmpl +++ b/templates/user/settings/security/webauthn.tmpl @@ -1,6 +1,6 @@ <h4 class="ui top attached header">{{ctx.Locale.Tr "settings.webauthn"}}</h4> <div class="ui attached segment"> - <p>{{ctx.Locale.Tr "settings.webauthn_desc" | SanitizeHTML}}</p> + <p>{{ctx.Locale.Tr "settings.webauthn_desc"}}</p> <p>{{ctx.Locale.Tr "settings.webauthn_key_loss_warning"}} {{ctx.Locale.Tr "settings.webauthn_alternative_tip"}}</p> {{template "user/auth/webauthn_error" .}} <div class="flex-list"> From 963df8290784d82385f7e8ad9f5c9abfd2fa2860 Mon Sep 17 00:00:00 2001 From: Jason Song <i@wolfogre.com> Date: Wed, 6 Mar 2024 17:56:04 +0800 Subject: [PATCH 21/62] Detect broken git hooks (#29494) Detect broken git hooks by checking if the commit id of branches in DB is the same with the git repo. It can help #29338 #28277 and maybe more issues. Users could complain about actions, webhooks, and activities not working, but they were not aware that it is caused by broken git hooks unless they could see a warning. <img width="1348" alt="image" src="https://github.com/go-gitea/gitea/assets/9418365/2b92a46d-7f1d-4115-bef4-9f970bd695da"> It should be merged after #29493. Otherwise, users could see a ephemeral warning after committing and opening the repo home page immediately. And it also waits for #29495, since the doc link (the anchor part) will be updated. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Giteabot <teabot@gitea.io> (cherry picked from commit a4bcfb8ef1d5b2b522f78c9560d53ddbdbb02218) --- options/locale/locale_en-US.ini | 1 + routers/web/repo/view.go | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 32aab26ffa..da9fe91075 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2671,6 +2671,7 @@ find_file.no_matching = No matching file found error.csv.too_large = Can't render this file because it is too large. error.csv.unexpected = Can't render this file because it contains an unexpected character in line %d and column %d. error.csv.invalid_field_count = Can't render this file because it has a wrong number of fields in line %d. +error.broken_git_hook = Git hooks of this repository seem to be broken. Please follow the <a target="_blank" rel="noreferrer" href="%s">documentation</a> to fix them, then push some commits to refresh the status. [graphs] component_loading = Loading %s... diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 9f8d1fcdfd..2670499d4f 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -996,6 +996,8 @@ func renderCode(ctx *context.Context) { return } + checkOutdatedBranch(ctx) + checkCitationFile(ctx, entry) if ctx.Written() { return @@ -1121,6 +1123,31 @@ PostRecentBranchCheck: ctx.HTML(http.StatusOK, tplRepoHome) } +func checkOutdatedBranch(ctx *context.Context) { + if !(ctx.Repo.IsAdmin() || ctx.Repo.IsOwner()) { + return + } + + // get the head commit of the branch since ctx.Repo.CommitID is not always the head commit of `ctx.Repo.BranchName` + commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName) + if err != nil { + log.Error("GetBranchCommitID: %v", err) + // Don't return an error page, as it can be rechecked the next time the user opens the page. + return + } + + dbBranch, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, ctx.Repo.BranchName) + if err != nil { + log.Error("GetBranch: %v", err) + // Don't return an error page, as it can be rechecked the next time the user opens the page. + return + } + + if dbBranch.CommitID != commit.ID.String() { + ctx.Flash.Warning(ctx.Tr("repo.error.broken_git_hook", "https://docs.gitea.com/help/faq#push-hook--webhook--actions-arent-running"), true) + } +} + // RenderUserCards render a page show users according the input template func RenderUserCards(ctx *context.Context, total int, getter func(opts db.ListOptions) ([]*user_model.User, error), tpl base.TplName) { page := ctx.FormInt("page") From ee4443c9982a5953d5c5a39e68b700f156bde1af Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Wed, 6 Mar 2024 19:23:27 +0900 Subject: [PATCH 22/62] Fix wrong header of org project view page (#29626) Follow #29248 The project view page still using `user/overview/header.tmpl` Before:  After:  (cherry picked from commit c6cc392b55b71ae1787e86e75b7121c3769adcbe) --- templates/user/overview/header.tmpl | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/templates/user/overview/header.tmpl b/templates/user/overview/header.tmpl index 4fdaa70d87..cf5e21fa62 100644 --- a/templates/user/overview/header.tmpl +++ b/templates/user/overview/header.tmpl @@ -29,19 +29,21 @@ </a> {{end}} - <a class="{{if eq .TabName "activity"}}active {{end}}item" href="{{.ContextUser.HomeLink}}?tab=activity"> - {{svg "octicon-rss"}} {{ctx.Locale.Tr "user.activity"}} - </a> - {{if not .DisableStars}} - <a class="{{if eq .TabName "stars"}}active {{end}}item" href="{{.ContextUser.HomeLink}}?tab=stars"> - {{svg "octicon-star"}} {{ctx.Locale.Tr "user.starred"}} - {{if .ContextUser.NumStars}} - <div class="ui small label">{{.ContextUser.NumStars}}</div> - {{end}} - </a> - {{else}} - <a class="{{if eq .TabName "watching"}}active {{end}}item" href="{{.ContextUser.HomeLink}}?tab=watching"> - {{svg "octicon-eye"}} {{ctx.Locale.Tr "user.watched"}} + {{if .ContextUser.IsIndividual}} + <a class="{{if eq .TabName "activity"}}active {{end}}item" href="{{.ContextUser.HomeLink}}?tab=activity"> + {{svg "octicon-rss"}} {{ctx.Locale.Tr "user.activity"}} </a> + {{if not .DisableStars}} + <a class="{{if eq .TabName "stars"}}active {{end}}item" href="{{.ContextUser.HomeLink}}?tab=stars"> + {{svg "octicon-star"}} {{ctx.Locale.Tr "user.starred"}} + {{if .ContextUser.NumStars}} + <div class="ui small label">{{.ContextUser.NumStars}}</div> + {{end}} + </a> + {{else}} + <a class="{{if eq .TabName "watching"}}active {{end}}item" href="{{.ContextUser.HomeLink}}?tab=watching"> + {{svg "octicon-eye"}} {{ctx.Locale.Tr "user.watched"}} + </a> + {{end}} {{end}} </div> From a8f5449cd95c06e8b55b7f776fc826fc7675c1b6 Mon Sep 17 00:00:00 2001 From: wxiaoguang <wxiaoguang@gmail.com> Date: Wed, 6 Mar 2024 19:50:39 +0800 Subject: [PATCH 23/62] Avoid unexpected panic in graceful manager (#29629) There is a fundamental design problem of the "manager" and the "wait group". If nothing has started, the "Wait" just panics: sync: WaitGroup is reused before previous Wait has returned There is no clear solution besides a complete rewriting of the "manager" If there are some mistakes in the app.ini, end users would just see the "panic", but not the real error messages. A real case: #27643 This PR is just a quick fix for the annoying panic problem. (cherry picked from commit 90a3f2d4b7ed3890d9655c0334444f86d89b7b30) --- modules/graceful/manager_unix.go | 10 +++++++++- modules/graceful/manager_windows.go | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go index f4af4993d9..edf5fc248f 100644 --- a/modules/graceful/manager_unix.go +++ b/modules/graceful/manager_unix.go @@ -59,7 +59,15 @@ func (g *Manager) start() { go func() { defer close(startupDone) // Wait till we're done getting all the listeners and then close the unused ones - g.createServerWaitGroup.Wait() + func() { + // FIXME: there is a fundamental design problem of the "manager" and the "wait group". + // If nothing has started, the "Wait" just panics: sync: WaitGroup is reused before previous Wait has returned + // There is no clear solution besides a complete rewriting of the "manager" + defer func() { + _ = recover() + }() + g.createServerWaitGroup.Wait() + }() // Ignore the error here there's not much we can do with it, they're logged in the CloseProvidedListeners function _ = CloseProvidedListeners() g.notify(readyMsg) diff --git a/modules/graceful/manager_windows.go b/modules/graceful/manager_windows.go index 0248dcb24d..ecf30af3f3 100644 --- a/modules/graceful/manager_windows.go +++ b/modules/graceful/manager_windows.go @@ -150,7 +150,15 @@ func (g *Manager) awaitServer(limit time.Duration) bool { c := make(chan struct{}) go func() { defer close(c) - g.createServerWaitGroup.Wait() + func() { + // FIXME: there is a fundamental design problem of the "manager" and the "wait group". + // If nothing has started, the "Wait" just panics: sync: WaitGroup is reused before previous Wait has returned + // There is no clear solution besides a complete rewriting of the "manager" + defer func() { + _ = recover() + }() + g.createServerWaitGroup.Wait() + }() }() if limit > 0 { select { From 95e682f10491c101c9b4ba716967790b271eba93 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Wed, 6 Mar 2024 20:17:19 +0800 Subject: [PATCH 24/62] Cache repository default branch commit status to reduce query on commit status table (#29444) After repository commit status has been introduced on dashaboard, the most top SQL comes from `GetLatestCommitStatusForPairs`. This PR adds a cache for the repository's default branch's latest combined commit status. When a new commit status updated, the cache will be marked as invalid. <img width="998" alt="image" src="https://github.com/go-gitea/gitea/assets/81045/76759de7-3a83-4d54-8571-278f5422aed3"> (cherry picked from commit e308d25f1b2fe24b4735432b05e5e221879a2705) --- routers/api/v1/repo/status.go | 4 +- routers/web/repo/repo.go | 28 ++-- .../repository/commitstatus/commitstatus.go | 135 ++++++++++++++++++ services/repository/files/commit.go | 48 ------- 4 files changed, 145 insertions(+), 70 deletions(-) create mode 100644 services/repository/commitstatus/commitstatus.go diff --git a/routers/api/v1/repo/status.go b/routers/api/v1/repo/status.go index 53711bedeb..9e36ea0aed 100644 --- a/routers/api/v1/repo/status.go +++ b/routers/api/v1/repo/status.go @@ -14,7 +14,7 @@ import ( "code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" - files_service "code.gitea.io/gitea/services/repository/files" + commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus" ) // NewCommitStatus creates a new CommitStatus @@ -64,7 +64,7 @@ func NewCommitStatus(ctx *context.APIContext) { Description: form.Description, Context: form.Context, } - if err := files_service.CreateCommitStatus(ctx, ctx.Repo.Repository, ctx.Doer, sha, status); err != nil { + if err := commitstatus_service.CreateCommitStatus(ctx, ctx.Repo.Repository, ctx.Doer, sha, status); err != nil { ctx.Error(http.StatusInternalServerError, "CreateCommitStatus", err) return } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 77aab8354b..57723e6a9a 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -35,6 +35,7 @@ import ( "code.gitea.io/gitea/services/forms" repo_service "code.gitea.io/gitea/services/repository" archiver_service "code.gitea.io/gitea/services/repository/archiver" + commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus" ) const ( @@ -630,30 +631,14 @@ func SearchRepo(ctx *context.Context) { return } - // collect the latest commit of each repo - // at most there are dozens of repos (limited by MaxResponseItems), so it's not a big problem at the moment - repoBranchNames := make(map[int64]string, len(repos)) - for _, repo := range repos { - repoBranchNames[repo.ID] = repo.DefaultBranch - } - - repoIDsToLatestCommitSHAs, err := git_model.FindBranchesByRepoAndBranchName(ctx, repoBranchNames) + latestCommitStatuses, err := commitstatus_service.FindReposLastestCommitStatuses(ctx, repos) if err != nil { - log.Error("FindBranchesByRepoAndBranchName: %v", err) - return - } - - // call the database O(1) times to get the commit statuses for all repos - repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoIDsToLatestCommitSHAs, db.ListOptionsAll) - if err != nil { - log.Error("GetLatestCommitStatusForPairs: %v", err) + log.Error("FindReposLastestCommitStatuses: %v", err) return } results := make([]*repo_service.WebSearchRepository, len(repos)) for i, repo := range repos { - latestCommitStatus := git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID]) - results[i] = &repo_service.WebSearchRepository{ Repository: &api.Repository{ ID: repo.ID, @@ -667,8 +652,11 @@ func SearchRepo(ctx *context.Context) { Link: repo.Link(), Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate, }, - LatestCommitStatus: latestCommitStatus, - LocaleLatestCommitStatus: latestCommitStatus.LocaleString(ctx.Locale), + } + + if latestCommitStatuses[i] != nil { + results[i].LatestCommitStatus = latestCommitStatuses[i] + results[i].LocaleLatestCommitStatus = latestCommitStatuses[i].LocaleString(ctx.Locale) } } diff --git a/services/repository/commitstatus/commitstatus.go b/services/repository/commitstatus/commitstatus.go new file mode 100644 index 0000000000..145fc7d53c --- /dev/null +++ b/services/repository/commitstatus/commitstatus.go @@ -0,0 +1,135 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package commitstatus + +import ( + "context" + "crypto/sha256" + "fmt" + + "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/log" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/services/automerge" +) + +func getCacheKey(repoID int64, brancheName string) string { + hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%d:%s", repoID, brancheName))) + return fmt.Sprintf("commit_status:%x", hashBytes) +} + +func updateCommitStatusCache(ctx context.Context, repoID int64, branchName string, status api.CommitStatusState) error { + c := cache.GetCache() + return c.Put(getCacheKey(repoID, branchName), string(status), 3*24*60) +} + +func deleteCommitStatusCache(ctx context.Context, repoID int64, branchName string) error { + c := cache.GetCache() + return c.Delete(getCacheKey(repoID, branchName)) +} + +// CreateCommitStatus creates a new CommitStatus given a bunch of parameters +// NOTE: All text-values will be trimmed from whitespaces. +// Requires: Repo, Creator, SHA +func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creator *user_model.User, sha string, status *git_model.CommitStatus) error { + repoPath := repo.RepoPath() + + // confirm that commit is exist + gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo) + if err != nil { + return fmt.Errorf("OpenRepository[%s]: %w", repoPath, err) + } + defer closer.Close() + + objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName) + + commit, err := gitRepo.GetCommit(sha) + if err != nil { + return fmt.Errorf("GetCommit[%s]: %w", sha, err) + } + if len(sha) != objectFormat.FullLength() { + // use complete commit sha + sha = commit.ID.String() + } + + if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{ + Repo: repo, + Creator: creator, + SHA: commit.ID, + CommitStatus: status, + }); err != nil { + return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err) + } + + defaultBranchCommit, err := gitRepo.GetBranchCommit(repo.DefaultBranch) + if err != nil { + return fmt.Errorf("GetBranchCommit[%s]: %w", repo.DefaultBranch, err) + } + + if commit.ID.String() == defaultBranchCommit.ID.String() { // since one commit status updated, the combined commit status should be invalid + if err := deleteCommitStatusCache(ctx, repo.ID, repo.DefaultBranch); err != nil { + log.Error("deleteCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err) + } + } + + if status.State.IsSuccess() { + if err := automerge.MergeScheduledPullRequest(ctx, sha, repo); err != nil { + return fmt.Errorf("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err) + } + } + + return nil +} + +// FindReposLastestCommitStatuses loading repository default branch latest combinded commit status with cache +func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) { + results := make([]*git_model.CommitStatus, len(repos)) + c := cache.GetCache() + + for i, repo := range repos { + status, ok := c.Get(getCacheKey(repo.ID, repo.DefaultBranch)).(string) + if ok && status != "" { + results[i] = &git_model.CommitStatus{State: api.CommitStatusState(status)} + } + } + + // collect the latest commit of each repo + // at most there are dozens of repos (limited by MaxResponseItems), so it's not a big problem at the moment + repoBranchNames := make(map[int64]string, len(repos)) + for i, repo := range repos { + if results[i] == nil { + repoBranchNames[repo.ID] = repo.DefaultBranch + } + } + + repoIDsToLatestCommitSHAs, err := git_model.FindBranchesByRepoAndBranchName(ctx, repoBranchNames) + if err != nil { + return nil, fmt.Errorf("FindBranchesByRepoAndBranchName: %v", err) + } + + // call the database O(1) times to get the commit statuses for all repos + repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoIDsToLatestCommitSHAs, db.ListOptionsAll) + if err != nil { + return nil, fmt.Errorf("GetLatestCommitStatusForPairs: %v", err) + } + + for i, repo := range repos { + if results[i] == nil { + results[i] = git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID]) + if results[i].State != "" { + if err := updateCommitStatusCache(ctx, repo.ID, repo.DefaultBranch, results[i].State); err != nil { + log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err) + } + } + } + } + + return results, nil +} diff --git a/services/repository/files/commit.go b/services/repository/files/commit.go index 512aec7c81..e0dad29273 100644 --- a/services/repository/files/commit.go +++ b/services/repository/files/commit.go @@ -5,61 +5,13 @@ package files import ( "context" - "fmt" asymkey_model "code.gitea.io/gitea/models/asymkey" - git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" - user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/services/automerge" ) -// CreateCommitStatus creates a new CommitStatus given a bunch of parameters -// NOTE: All text-values will be trimmed from whitespaces. -// Requires: Repo, Creator, SHA -func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creator *user_model.User, sha string, status *git_model.CommitStatus) error { - repoPath := repo.RepoPath() - - // confirm that commit is exist - gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo) - if err != nil { - return fmt.Errorf("OpenRepository[%s]: %w", repoPath, err) - } - defer closer.Close() - - objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName) - - commit, err := gitRepo.GetCommit(sha) - if err != nil { - gitRepo.Close() - return fmt.Errorf("GetCommit[%s]: %w", sha, err) - } else if len(sha) != objectFormat.FullLength() { - // use complete commit sha - sha = commit.ID.String() - } - gitRepo.Close() - - if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{ - Repo: repo, - Creator: creator, - SHA: commit.ID, - CommitStatus: status, - }); err != nil { - return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err) - } - - if status.State.IsSuccess() { - if err := automerge.MergeScheduledPullRequest(ctx, sha, repo); err != nil { - return fmt.Errorf("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err) - } - } - - return nil -} - // CountDivergingCommits determines how many commits a branch is ahead or behind the repository's base branch func CountDivergingCommits(ctx context.Context, repo *repo_model.Repository, branch string) (*git.DivergeObject, error) { divergence, err := git.GetDivergingCommits(ctx, repo.RepoPath(), repo.DefaultBranch, branch) From a7830e8d87cf8cf5c02f682a985a7806efd171ca Mon Sep 17 00:00:00 2001 From: wxiaoguang <wxiaoguang@gmail.com> Date: Wed, 6 Mar 2024 21:12:44 +0800 Subject: [PATCH 25/62] Avoid issue info panic (#29625) Fix #29624 (cherry picked from commit 1d2548949adf6046f330d27084efce6e63330e04) --- models/activities/action.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/models/activities/action.go b/models/activities/action.go index 6df113138b..6593767679 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -395,10 +395,14 @@ func (a *Action) GetCreate() time.Time { return a.CreatedUnix.AsTime() } -// GetIssueInfos returns a list of issues associated with -// the action. +// GetIssueInfos returns a list of associated information with the action. func (a *Action) GetIssueInfos() []string { - return strings.SplitN(a.Content, "|", 3) + // make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length + ret := strings.SplitN(a.Content, "|", 3) + for len(ret) < 3 { + ret = append(ret, "") + } + return ret } // GetIssueTitle returns the title of first issue associated with the action. From 87e7a83edf4bac789e0ff68c4372cc3882c1b88b Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Wed, 6 Mar 2024 22:44:24 +0100 Subject: [PATCH 26/62] Tweak actions color and borders (#29640) - Increase contrast overall - Unalias the ansi color in dark theme and copy them to light - Add outer border - Add border radius <img width="1337" alt="Screenshot 2024-03-06 at 22 30 03" src="https://github.com/go-gitea/gitea/assets/115237/11407c0f-0bb2-435e-a034-22b1f106d9b0"> <img width="1335" alt="Screenshot 2024-03-06 at 22 36 59" src="https://github.com/go-gitea/gitea/assets/115237/267db442-0979-4acc-a79e-8579b4cb0262"> (cherry picked from commit 16f13265143ff08cb6c33e976998b262e94fe569) --- web_src/css/themes/theme-gitea-dark.css | 36 ++++++++++---------- web_src/css/themes/theme-gitea-light.css | 42 ++++++++++++------------ web_src/js/components/RepoActionView.vue | 8 +++-- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/web_src/css/themes/theme-gitea-dark.css b/web_src/css/themes/theme-gitea-dark.css index ac77b7bbd9..c187888a38 100644 --- a/web_src/css/themes/theme-gitea-dark.css +++ b/web_src/css/themes/theme-gitea-dark.css @@ -61,10 +61,10 @@ --color-secondary-hover: var(--color-secondary-dark-3); --color-secondary-active: var(--color-secondary-dark-2); /* console colors - used for actions console and console files */ - --color-console-fg: #d9dde2; - --color-console-fg-subtle: #95989c; - --color-console-bg: #1c2023; - --color-console-border: #272b2e; + --color-console-fg: #f8f8f9; + --color-console-fg-subtle: #bec4c8; + --color-console-bg: #181b1d; + --color-console-border: #313538; --color-console-hover-bg: #ffffff16; --color-console-active-bg: #313538; --color-console-menu-bg: #272b2e; @@ -122,21 +122,21 @@ --color-brown-dark-2: #835b42; --color-black-dark-2: #292a2e; /* ansi colors used for actions console and console files */ - --color-ansi-black: var(--color-black); - --color-ansi-red: var(--color-red); - --color-ansi-green: var(--color-green); - --color-ansi-yellow: var(--color-yellow); - --color-ansi-blue: var(--color-blue); - --color-ansi-magenta: var(--color-pink); - --color-ansi-cyan: var(--color-teal); + --color-ansi-black: #1f2326; + --color-ansi-red: #cc4848; + --color-ansi-green: #87ab63; + --color-ansi-yellow: #cc9903; + --color-ansi-blue: #3a8ac6; + --color-ansi-magenta: #d22e8b; + --color-ansi-cyan: #00918a; --color-ansi-white: var(--color-console-fg-subtle); - --color-ansi-bright-black: var(--color-black-light); - --color-ansi-bright-red: var(--color-red-light); - --color-ansi-bright-green: var(--color-green-light); - --color-ansi-bright-yellow: var(--color-yellow-light); - --color-ansi-bright-blue: var(--color-blue-light); - --color-ansi-bright-magenta: var(--color-pink-light); - --color-ansi-bright-cyan: var(--color-teal-light); + --color-ansi-bright-black: #46494d; + --color-ansi-bright-red: #d15a5a; + --color-ansi-bright-green: #93b373; + --color-ansi-bright-yellow: #eaaf03; + --color-ansi-bright-blue: #4e96cc; + --color-ansi-bright-magenta: #d74397; + --color-ansi-bright-cyan: #00b6ad; --color-ansi-bright-white: var(--color-console-fg); /* other colors */ --color-grey: #3c4043; diff --git a/web_src/css/themes/theme-gitea-light.css b/web_src/css/themes/theme-gitea-light.css index 5c375712d8..5137e0774c 100644 --- a/web_src/css/themes/theme-gitea-light.css +++ b/web_src/css/themes/theme-gitea-light.css @@ -61,14 +61,14 @@ --color-secondary-hover: var(--color-secondary-dark-5); --color-secondary-active: var(--color-secondary-dark-6); /* console colors - used for actions console and console files */ - --color-console-fg: #eeeff2; - --color-console-fg-subtle: #959cab; - --color-console-bg: #262936; - --color-console-border: #383c47; + --color-console-fg: #f8f8f9; + --color-console-fg-subtle: #bec4c8; + --color-console-bg: #181b1d; + --color-console-border: #313538; --color-console-hover-bg: #ffffff16; - --color-console-active-bg: #454a57; - --color-console-menu-bg: #383c47; - --color-console-menu-border: #5c6374; + --color-console-active-bg: #313538; + --color-console-menu-bg: #272b2e; + --color-console-menu-border: #464a4d; /* named colors */ --color-red: #db2828; --color-orange: #f2711c; @@ -122,21 +122,21 @@ --color-brown-dark-2: #845232; --color-black-dark-2: #161617; /* ansi colors used for actions console and console files */ - --color-ansi-black: var(--color-black); - --color-ansi-red: var(--color-red); - --color-ansi-green: var(--color-green); - --color-ansi-yellow: var(--color-yellow); - --color-ansi-blue: var(--color-blue); - --color-ansi-magenta: var(--color-pink); - --color-ansi-cyan: var(--color-teal); + --color-ansi-black: #1f2326; + --color-ansi-red: #cc4848; + --color-ansi-green: #87ab63; + --color-ansi-yellow: #cc9903; + --color-ansi-blue: #3a8ac6; + --color-ansi-magenta: #d22e8b; + --color-ansi-cyan: #00918a; --color-ansi-white: var(--color-console-fg-subtle); - --color-ansi-bright-black: var(--color-black-light); - --color-ansi-bright-red: var(--color-red-light); - --color-ansi-bright-green: var(--color-green-light); - --color-ansi-bright-yellow: var(--color-yellow-light); - --color-ansi-bright-blue: var(--color-blue-light); - --color-ansi-bright-magenta: var(--color-pink-light); - --color-ansi-bright-cyan: var(--color-teal-light); + --color-ansi-bright-black: #46494d; + --color-ansi-bright-red: #d15a5a; + --color-ansi-bright-green: #93b373; + --color-ansi-bright-yellow: #eaaf03; + --color-ansi-bright-blue: #4e96cc; + --color-ansi-bright-magenta: #d74397; + --color-ansi-bright-cyan: #00b6ad; --color-ansi-bright-white: var(--color-console-fg); /* other colors */ --color-grey: #707070; diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 573c73d1b5..e1e5d1bc13 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -631,6 +631,8 @@ export function initRepositoryActionView() { width: 70%; display: flex; flex-direction: column; + border: 1px solid var(--color-console-border); + border-radius: var(--border-radius); } /* begin fomantic button overrides */ @@ -690,7 +692,6 @@ export function initRepositoryActionView() { justify-content: space-between; align-items: center; padding: 0 12px; - border-bottom: 1px solid var(--color-console-border); background-color: var(--color-console-bg); position: sticky; top: 0; @@ -714,6 +715,7 @@ export function initRepositoryActionView() { background-color: var(--color-console-bg); max-height: 100%; border-radius: 0 0 var(--border-radius) var(--border-radius); + border-top: 1px solid var(--color-console-border); z-index: 0; } @@ -799,7 +801,7 @@ export function initRepositoryActionView() { /* class names 'log-time-seconds' and 'log-time-stamp' are used in the method toggleTimeDisplay */ .job-log-line .line-num, .log-time-seconds { width: 48px; - color: var(--color-grey-light); + color: var(--color-text-light-3); text-align: right; user-select: none; } @@ -815,7 +817,7 @@ export function initRepositoryActionView() { .job-log-line .log-time, .log-time-stamp { - color: var(--color-grey-light); + color: var(--color-text-light-3); margin-left: 10px; white-space: nowrap; } From 3bdf1e0323d547cceebbc45852ece45adf1e9ca6 Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Wed, 6 Mar 2024 23:20:05 +0100 Subject: [PATCH 27/62] Update various logos and unify their filenames (#29637) 1. Checked all logos, updated 3 of them to newer versions. 2. Remove `open-with-` infix on the editor logos to be consistent with other files. <img width="626" alt="image" src="https://github.com/go-gitea/gitea/assets/115237/3b2d9486-6e0a-45c6-b0e4-d38dc4c0b118"> (cherry picked from commit 9730d3a9af889f27a1026045ab650710a01841e5) Conflicts: routers/web/repo/view.go web_src/svg/gitea-jetbrains.svg web_src/svg/gitea-vscodium.svg already in Forgejo --- public/assets/img/svg/gitea-bitbucket.svg | 2 +- public/assets/img/svg/gitea-facebook.svg | 2 +- public/assets/img/svg/gitea-jetbrains.svg | 1 + .../assets/img/svg/gitea-microsoftonline.svg | 2 +- public/assets/img/svg/gitea-vscodium.svg | 1 + web_src/svg/gitea-bitbucket.svg | 2 +- web_src/svg/gitea-facebook.svg | 2 +- web_src/svg/gitea-jetbrains.svg | 62 +++++++++++++++++++ web_src/svg/gitea-microsoftonline.svg | 2 +- web_src/svg/gitea-vscodium.svg | 1 + 10 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 public/assets/img/svg/gitea-jetbrains.svg create mode 100644 public/assets/img/svg/gitea-vscodium.svg create mode 100644 web_src/svg/gitea-jetbrains.svg create mode 100644 web_src/svg/gitea-vscodium.svg diff --git a/public/assets/img/svg/gitea-bitbucket.svg b/public/assets/img/svg/gitea-bitbucket.svg index b900335ea1..83e4c5c6e7 100644 --- a/public/assets/img/svg/gitea-bitbucket.svg +++ b/public/assets/img/svg/gitea-bitbucket.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="gitea-bitbucket__svg gitea-bitbucket__gitea-bitbucket svg gitea-bitbucket" preserveAspectRatio="xMinYMin meet" viewBox="0 0 256 295" width="16" height="16"><g fill="#205081"><path d="M128 0C57.732 0 .012 18.822.012 42.663c0 6.274 15.057 95.364 21.331 130.498 2.51 16.312 43.918 38.898 106.657 38.898 62.74 0 102.893-22.586 106.657-38.898 6.274-35.134 21.331-124.224 21.331-130.498C254.734 18.822 198.268 0 128 0m0 183.199c-22.586 0-40.153-17.567-40.153-40.153s17.567-40.153 40.153-40.153 40.153 17.567 40.153 40.153c0 21.331-17.567 40.153-40.153 40.153m0-127.988c-45.172 0-81.561-7.53-81.561-17.567 0-10.039 36.389-17.567 81.561-17.567s81.561 7.528 81.561 17.567c0 10.038-36.389 17.567-81.561 17.567"/><path d="M220.608 207.04c-2.51 0-3.764 1.255-3.764 1.255s-31.37 25.096-87.835 25.096c-56.466 0-87.835-25.096-87.835-25.096s-2.51-1.255-3.765-1.255c-2.51 0-5.019 1.255-5.019 5.02v1.254c5.02 26.35 8.784 45.172 8.784 47.682 3.764 18.822 41.408 33.88 86.58 33.88s82.816-15.058 86.58-33.88c0-2.51 3.765-21.332 8.784-47.682v-1.255c1.255-2.51 0-5.019-2.51-5.019"/><circle cx="128" cy="141.791" r="20.077"/></g></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 62.42 62.42" class="svg gitea-bitbucket" width="16" height="16" aria-hidden="true"><defs><linearGradient id="gitea-bitbucket__a" x1="64.01" x2="32.99" y1="30.27" y2="54.48" gradientUnits="userSpaceOnUse"><stop offset=".18" stop-color="#0052cc"/><stop offset="1" stop-color="#2684ff"/></linearGradient></defs><g data-name="Layer 2"><path fill="#2684ff" d="M2 3.13a2 2 0 0 0-2 2.32l8.49 51.54a2.72 2.72 0 0 0 2.66 2.27h40.73a2 2 0 0 0 2-1.68l8.49-52.12a2 2 0 0 0-2-2.32Zm35.75 37.25h-13l-3.52-18.39H40.9Z"/><path fill="url(#gitea-bitbucket__a)" d="M59.67 25.12H40.9l-3.15 18.39h-13L9.4 61.73a2.7 2.7 0 0 0 1.75.66h40.74a2 2 0 0 0 2-1.68Z" transform="translate(0 -3.13)"/></g></svg> \ No newline at end of file diff --git a/public/assets/img/svg/gitea-facebook.svg b/public/assets/img/svg/gitea-facebook.svg index cbeb76b127..6101becad2 100644 --- a/public/assets/img/svg/gitea-facebook.svg +++ b/public/assets/img/svg/gitea-facebook.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="gitea-facebook__svg gitea-facebook__gitea-facebook svg gitea-facebook" style="shape-rendering:geometricPrecision;text-rendering:geometricPrecision;image-rendering:optimizeQuality;fill-rule:evenodd;clip-rule:evenodd" viewBox="0 0 128 128" width="16" height="16"><path fill="#395b97" d="M93.5 8.5q-2.177 1.203-5 1.5L10 88.5q-.297 2.823-1.5 5a552 552 0 0 1-.5-56Q11.75 11.75 37.5 8a552 552 0 0 1 56 .5" style="opacity:.995"/><path fill="#366098" d="M93.5 8.5q23.832 6.337 26 31a677 677 0 0 0-1.5 37l-35 35a32.4 32.4 0 0 0-.5 8 442 442 0 0 1-1-42h14a380 380 0 0 0 3-17h-17q-3.75-20.745 17-18v-16q-38.632-4.865-33 34h-14v17h14v42q-14.01.25-28-.5-23.177-2.93-29-25.5 1.203-2.177 1.5-5L88.5 10q2.823-.297 5-1.5" style="opacity:.976"/><path fill="#346499" d="M119.5 39.5q.25 25.005-.5 50-5.432 30.368-36.5 30a32.4 32.4 0 0 1 .5-8l35-35q.254-18.76 1.5-37" style="opacity:.918"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" clip-rule="evenodd" image-rendering="optimizeQuality" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" viewBox="0 0 14222 14222" class="svg gitea-facebook" width="16" height="16" aria-hidden="true"><g fill-rule="nonzero"><path fill="#1977f3" d="M14222 7111C14222 3184 11038 0 7111 0S0 3184 0 7111c0 3549 2600 6491 6000 7025V9167H4194V7111h1806V5544c0-1782 1062-2767 2686-2767 778 0 1592 139 1592 139v1750h-897c-883 0-1159 548-1159 1111v1334h1972l-315 2056H8222v4969c3400-533 6000-3475 6000-7025"/><path fill="#fefefe" d="m9879 9167 315-2056H8222V5777c0-562 275-1111 1159-1111h897V2916s-814-139-1592-139c-1624 0-2686 984-2686 2767v1567H4194v2056h1806v4969c362 57 733 86 1111 86s749-30 1111-86V9167z"/></g></svg> \ No newline at end of file diff --git a/public/assets/img/svg/gitea-jetbrains.svg b/public/assets/img/svg/gitea-jetbrains.svg new file mode 100644 index 0000000000..5821736225 --- /dev/null +++ b/public/assets/img/svg/gitea-jetbrains.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 70 70" class="svg gitea-jetbrains" width="16" height="16" aria-hidden="true"><linearGradient id="gitea-jetbrains__a" x1=".79" x2="33.317" y1="40.089" y2="40.089" gradientUnits="userSpaceOnUse"><stop offset=".258" style="stop-color:#f97a12"/><stop offset=".459" style="stop-color:#b07b58"/><stop offset=".724" style="stop-color:#577bae"/><stop offset=".91" style="stop-color:#1e7ce5"/><stop offset="1" style="stop-color:#087cfa"/></linearGradient><path d="M17.7 54.6.8 41.2l8.4-15.6L33.3 35z" style="fill:url(#gitea-jetbrains__a)"/><linearGradient id="gitea-jetbrains__b" x1="25.767" x2="79.424" y1="24.88" y2="54.57" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#f97a12"/><stop offset=".072" style="stop-color:#cb7a3e"/><stop offset=".154" style="stop-color:#9e7b6a"/><stop offset=".242" style="stop-color:#757b91"/><stop offset=".334" style="stop-color:#537bb1"/><stop offset=".432" style="stop-color:#387ccc"/><stop offset=".538" style="stop-color:#237ce0"/><stop offset=".655" style="stop-color:#147cef"/><stop offset=".792" style="stop-color:#0b7cf7"/><stop offset="1" style="stop-color:#087cfa"/></linearGradient><path d="m70 18.7-1.3 40.5L41.8 70 25.6 59.6 49.3 35 38.9 12.3l9.3-11.2z" style="fill:url(#gitea-jetbrains__b)"/><linearGradient id="gitea-jetbrains__c" x1="63.228" x2="48.29" y1="42.915" y2="-1.719" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#fe315d"/><stop offset=".078" style="stop-color:#cb417e"/><stop offset=".16" style="stop-color:#9e4e9b"/><stop offset=".247" style="stop-color:#755bb4"/><stop offset=".339" style="stop-color:#5365ca"/><stop offset=".436" style="stop-color:#386ddb"/><stop offset=".541" style="stop-color:#2374e9"/><stop offset=".658" style="stop-color:#1478f3"/><stop offset=".794" style="stop-color:#0b7bf8"/><stop offset="1" style="stop-color:#087cfa"/></linearGradient><path d="M70 18.7 48.7 43.9l-9.8-31.6 9.3-11.2z" style="fill:url(#gitea-jetbrains__c)"/><linearGradient id="gitea-jetbrains__d" x1="10.72" x2="55.524" y1="16.473" y2="90.58" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#fe315d"/><stop offset=".04" style="stop-color:#f63462"/><stop offset=".104" style="stop-color:#df3a71"/><stop offset=".167" style="stop-color:#c24383"/><stop offset=".291" style="stop-color:#ad4a91"/><stop offset=".55" style="stop-color:#755bb4"/><stop offset=".917" style="stop-color:#1d76ed"/><stop offset="1" style="stop-color:#087cfa"/></linearGradient><path d="M33.7 58.1 5.6 68.3l4.5-15.8L16 33.1 0 27.7 10.1 0l22 2.7 21.6 24.7z" style="fill:url(#gitea-jetbrains__d)"/><path d="M13.7 13.5h43.2v43.2H13.7z" style="fill:#000"/><path d="M17.7 48.6h16.2v2.7H17.7zM29.4 22.4v-3.3h-9v3.3H23v11.3h-2.6V37h9v-3.3h-2.5V22.4zM38 37.3c-1.4 0-2.6-.3-3.5-.8s-1.7-1.2-2.3-1.9l2.5-2.8c.5.6 1 1 1.5 1.3s1.1.5 1.7.5c.7 0 1.3-.2 1.8-.7.4-.5.6-1.2.6-2.3V19.1h4v11.7c0 1.1-.1 2-.4 2.8s-.7 1.4-1.3 2c-.5.5-1.2 1-2 1.2-.8.3-1.6.5-2.6.5" style="fill:#fff"/></svg> \ No newline at end of file diff --git a/public/assets/img/svg/gitea-microsoftonline.svg b/public/assets/img/svg/gitea-microsoftonline.svg index ce4f1a5c8f..f2ce13ac22 100644 --- a/public/assets/img/svg/gitea-microsoftonline.svg +++ b/public/assets/img/svg/gitea-microsoftonline.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="gitea-microsoftonline__svg gitea-microsoftonline__gitea-microsoftonline svg gitea-microsoftonline" viewBox="0 0 2075 2499.8" width="16" height="16"><path fill="#eb3c00" d="M0 2016.6V496.8L1344.4 0 2075 233.7v2045.9l-730.6 220.3zl1344.4 161.8V409.2L467.6 613.8v1198.3z"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48" class="svg gitea-microsoftonline" width="16" height="16" aria-hidden="true"><path fill="url(#gitea-microsoftonline__a)" d="m20.084 3.026-.224.136a8 8 0 0 0-1.009.722l.648-.456H25L26 11l-5 5-5 3.475v4.008a8 8 0 0 0 3.857 6.844l5.264 3.186L14 40h-2.145l-3.998-2.42A8 8 0 0 1 4 30.737V17.26a8 8 0 0 1 3.86-6.846l12-7.258q.111-.068.224-.131Z"/><path fill="url(#gitea-microsoftonline__b)" d="m20.084 3.026-.224.136a8 8 0 0 0-1.009.722l.648-.456H25L26 11l-5 5-5 3.475v4.008a8 8 0 0 0 3.857 6.844l5.264 3.186L14 40h-2.145l-3.998-2.42A8 8 0 0 1 4 30.737V17.26a8 8 0 0 1 3.86-6.846l12-7.258q.111-.068.224-.131Z"/><path fill="url(#gitea-microsoftonline__c)" d="M32 19v4.48a8 8 0 0 1-3.857 6.844l-12 7.264a8 8 0 0 1-8.008.16l11.722 7.096a8 8 0 0 0 8.286 0l12-7.264A8 8 0 0 0 44 30.736V27.5L43 26z"/><path fill="url(#gitea-microsoftonline__d)" d="M32 19v4.48a8 8 0 0 1-3.857 6.844l-12 7.264a8 8 0 0 1-8.008.16l11.722 7.096a8 8 0 0 0 8.286 0l12-7.264A8 8 0 0 0 44 30.736V27.5L43 26z"/><path fill="url(#gitea-microsoftonline__e)" d="m40.14 10.415-12-7.258a8 8 0 0 0-8.042-.139l-.238.144A8 8 0 0 0 16 10.008v9.483l3.86-2.334a8 8 0 0 1 8.28 0l12 7.258A8 8 0 0 1 43.997 31q.004-.132.004-.263V17.26a8 8 0 0 0-3.86-6.845Z"/><path fill="url(#gitea-microsoftonline__f)" d="m40.14 10.415-12-7.258a8 8 0 0 0-8.042-.139l-.238.144A8 8 0 0 0 16 10.008v9.483l3.86-2.334a8 8 0 0 1 8.28 0l12 7.258A8 8 0 0 1 43.997 31q.004-.132.004-.263V17.26a8 8 0 0 0-3.86-6.845Z"/><path fill="url(#gitea-microsoftonline__g)" d="M4.004 30.998"/><path fill="url(#gitea-microsoftonline__h)" d="M4.004 30.998"/><defs><radialGradient id="gitea-microsoftonline__a" cx="0" cy="0" r="1" gradientTransform="rotate(110.528 5.021 11.358)scale(33.3657 58.1966)" gradientUnits="userSpaceOnUse"><stop offset=".064" stop-color="#AE7FE2"/><stop offset="1" stop-color="#0078D4"/></radialGradient><radialGradient id="gitea-microsoftonline__c" cx="0" cy="0" r="1" gradientTransform="matrix(30.7198 -4.51832 2.98465 20.29248 10.43 36.351)" gradientUnits="userSpaceOnUse"><stop offset=".134" stop-color="#D59DFF"/><stop offset="1" stop-color="#5E438F"/></radialGradient><radialGradient id="gitea-microsoftonline__e" cx="0" cy="0" r="1" gradientTransform="matrix(-24.1583 -6.12555 10.3118 -40.66824 41.055 26.504)" gradientUnits="userSpaceOnUse"><stop offset=".058" stop-color="#50E6FF"/><stop offset="1" stop-color="#436DCD"/></radialGradient><radialGradient id="gitea-microsoftonline__g" cx="0" cy="0" r="1" gradientTransform="matrix(-24.1583 -6.12555 10.3118 -40.66824 41.055 26.504)" gradientUnits="userSpaceOnUse"><stop offset=".058" stop-color="#50E6FF"/><stop offset="1" stop-color="#436DCD"/></radialGradient><linearGradient id="gitea-microsoftonline__b" x1="17.512" x2="12.751" y1="37.868" y2="29.635" gradientUnits="userSpaceOnUse"><stop stop-color="#114A8B"/><stop offset="1" stop-color="#0078D4" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__d" x1="40.357" x2="35.255" y1="25.377" y2="32.692" gradientUnits="userSpaceOnUse"><stop stop-color="#493474"/><stop offset="1" stop-color="#8C66BA" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__f" x1="16.976" x2="24.487" y1="3.057" y2="3.057" gradientUnits="userSpaceOnUse"><stop stop-color="#2D3F80"/><stop offset="1" stop-color="#436DCD" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__h" x1="16.976" x2="24.487" y1="3.057" y2="3.057" gradientUnits="userSpaceOnUse"><stop stop-color="#2D3F80"/><stop offset="1" stop-color="#436DCD" stop-opacity="0"/></linearGradient></defs></svg> \ No newline at end of file diff --git a/public/assets/img/svg/gitea-vscodium.svg b/public/assets/img/svg/gitea-vscodium.svg new file mode 100644 index 0000000000..6aad3d3a64 --- /dev/null +++ b/public/assets/img/svg/gitea-vscodium.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" clip-rule="evenodd" viewBox="0 0 16 16" class="svg gitea-vscodium" width="16" height="16" aria-hidden="true"><path fill-rule="nonzero" d="m10.2.2.5-.3c.3 0 .5.2.7.4l.2.8-.2 1-.8 2.4c-.3 1-.4 2 0 2.9l.8-2c.2 0 .4.1.4.3l-.3 1L9.2 13l3.1-2.9c.3-.2.7-.5.8-1a2 2 0 0 0-.3-1c-.2-.5-.5-.9-.6-1.4l.1-.7c.1-.1.3-.2.5-.1.2 0 .3.2.4.4.3.5.4 1.2.5 1.8l.6-1.2c0-.2.2-.4.4-.6l.4-.2c.2 0 .4.3.4.4v.6l-.8 1.6-1.4 1.8 1-.4c.2 0 .6.2.7.5 0 .2 0 .4-.2.5-.3.2-.6.2-1 .2-1 0-2.2.6-2.9 1.4L9.6 15c-.4.4-.9 1-1.4.8-.8-.1-.8-1.3-1-1.8 0-.3-.2-.6-.4-.7-.3-.2-.5-.3-.8-.3-.6-.1-1.2 0-1.8-.2l-.8-.4-.4-.7c-.3-.6-.3-1.2-.5-1.8A4 4 0 0 0 1 8l-.4-.4v-.4c.2-.2.5-.2.7 0 .5.2.5.8 1 1.1V6.2s.3-.1.4 0l.2.5L3 9c.4-.4.6-1 .5-1.5L3.4 7l.3-.2c.2 0 .3.2.4.3v.7c0 .6-.3 1.1-.4 1.7-.2.4-.3 1-.1 1.4.1.5.5.9.9 1 .5.3 1.1.4 1.7.4-.4-.6-.7-1.2-.7-2 0-.7.4-1.3.6-2C6.3 7 5.7 5.8 4.8 5l-1.5-.7c-.4-.2-.7-.7-.7-1.2.3-.1.7 0 1 .1L5 4.5l.6.1c.2-.3 0-.6-.2-.8-.3-.5-1-.6-1.3-1a.9.9 0 0 1-.2-.8c0-.2.3-.4.5-.4.4 0 .7.3.9.5.8.8 1.2 1.8 1.4 3s0 2.5-.2 3.7c0 .3-.2.5-.1.8l.2.2c.2 0 .4 0 .5-.2.4-.3.8-.8.9-1.3l.1-1.2.1-.6.4-.2.3.3v.6c-.1.5-.2 1-.5 1.6a2 2 0 0 1-.6 1l-1 1c-.1.2-.2.6-.1.9 0 .2.2.4.4.5.4.2.8.2 1 0 .3-.1.5-.4.7-.6l.5-1.4.4-2.5C9.7 7 9.6 6 9 5.2c-.2-.4-.5-.7-1-1l-1-.8c-.2-.3-.4-.7-.3-1.2h.6c.4.1.7.4.9.8s.4.8.9 1l-1-2c-.1-.3-.3-.5-.2-.8 0-.2.2-.4.4-.4s.4.1.5.3l.2.5 1 3.1a4 4 0 0 0 .4-2.3L10 1V.2Z"/></svg> \ No newline at end of file diff --git a/web_src/svg/gitea-bitbucket.svg b/web_src/svg/gitea-bitbucket.svg index d3b15a9dc6..ac490c944f 100644 --- a/web_src/svg/gitea-bitbucket.svg +++ b/web_src/svg/gitea-bitbucket.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 256 295" class="svg gitea-bitbucket" width="16" height="16" aria-hidden="true"><g fill="#205081"><path d="M128 0C57.732 0 .012 18.822.012 42.663c0 6.274 15.057 95.364 21.331 130.498 2.51 16.312 43.918 38.898 106.657 38.898 62.74 0 102.893-22.586 106.657-38.898 6.274-35.134 21.331-124.224 21.331-130.498C254.734 18.822 198.268 0 128 0zm0 183.199c-22.586 0-40.153-17.567-40.153-40.153s17.567-40.153 40.153-40.153 40.153 17.567 40.153 40.153c0 21.331-17.567 40.153-40.153 40.153zm0-127.988c-45.172 0-81.561-7.53-81.561-17.567 0-10.039 36.389-17.567 81.561-17.567 45.172 0 81.561 7.528 81.561 17.567 0 10.038-36.389 17.567-81.561 17.567z"/><path d="M220.608 207.04c-2.51 0-3.764 1.255-3.764 1.255s-31.37 25.096-87.835 25.096c-56.466 0-87.835-25.096-87.835-25.096s-2.51-1.255-3.765-1.255c-2.51 0-5.019 1.255-5.019 5.02v1.254c5.02 26.35 8.784 45.172 8.784 47.682 3.764 18.822 41.408 33.88 86.58 33.88s82.816-15.058 86.58-33.88c0-2.51 3.765-21.332 8.784-47.682v-1.255c1.255-2.51 0-5.019-2.51-5.019z"/><circle cx="128" cy="141.791" r="20.077"/></g></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 62.42 62.42"><defs><linearGradient id="a" x1="64.01" x2="32.99" y1="30.27" y2="54.48" gradientUnits="userSpaceOnUse"><stop offset=".18" stop-color="#0052cc"/><stop offset="1" stop-color="#2684ff"/></linearGradient></defs><g data-name="Layer 2"><path fill="#2684ff" d="M2 3.13a2 2 0 0 0-2 2.32l8.49 51.54a2.72 2.72 0 0 0 2.66 2.27h40.73a2 2 0 0 0 2-1.68l8.49-52.12a2 2 0 0 0-2-2.32Zm35.75 37.25h-13l-3.52-18.39H40.9Z"/><path fill="url(#a)" d="M59.67 25.12H40.9l-3.15 18.39h-13L9.4 61.73a2.71 2.71 0 0 0 1.75.66h40.74a2 2 0 0 0 2-1.68Z" transform="translate(0 -3.13)"/></g></svg> \ No newline at end of file diff --git a/web_src/svg/gitea-facebook.svg b/web_src/svg/gitea-facebook.svg index 8163e2a966..68cd20750a 100644 --- a/web_src/svg/gitea-facebook.svg +++ b/web_src/svg/gitea-facebook.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" style="shape-rendering:geometricPrecision;text-rendering:geometricPrecision;image-rendering:optimizeQuality;fill-rule:evenodd;clip-rule:evenodd" viewBox="0 0 128 128" class="svg gitea-facebook" width="16" height="16" aria-hidden="true"><path fill="#395b97" d="M93.5 8.5c-1.452.802-3.118 1.302-5 1.5L10 88.5c-.198 1.882-.698 3.548-1.5 5a551.581 551.581 0 0 1-.5-56c2.5-17.167 12.333-27 29.5-29.5a551.581 551.581 0 0 1 56 .5Z" style="opacity:.995"/><path fill="#366098" d="M93.5 8.5c15.888 4.225 24.555 14.558 26 31a676.749 676.749 0 0 0-1.5 37l-35 35a32.438 32.438 0 0 0-.5 8 441.615 441.615 0 0 1-1-42h14a379.883 379.883 0 0 0 3-17h-17c-2.5-13.83 3.166-19.83 17-18v-16c-25.755-3.243-36.755 8.09-33 34h-14v17h14v42c-9.34.166-18.673 0-28-.5-15.451-1.953-25.118-10.453-29-25.5.802-1.452 1.302-3.118 1.5-5L88.5 10c1.882-.198 3.548-.698 5-1.5Z" style="opacity:.976"/><path fill="#346499" d="M119.5 39.5c.167 16.67 0 33.337-.5 50-3.622 20.245-15.788 30.245-36.5 30a32.438 32.438 0 0 1 .5-8l35-35c.169-12.507.669-24.84 1.5-37Z" style="opacity:.918"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" clip-rule="evenodd" image-rendering="optimizeQuality" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" viewBox="0 0 14222 14222"><g fill-rule="nonzero"><path fill="#1977f3" d="M14222 7111C14222 3184 11038 0 7111 0S0 3184 0 7111c0 3549 2600 6491 6000 7025V9167H4194V7111h1806V5544c0-1782 1062-2767 2686-2767 778 0 1592 139 1592 139v1750h-897c-883 0-1159 548-1159 1111v1334h1972l-315 2056H8222v4969c3400-533 6000-3475 6000-7025z"/><path fill="#fefefe" d="m9879 9167 315-2056H8222V5777c0-562 275-1111 1159-1111h897V2916s-814-139-1592-139c-1624 0-2686 984-2686 2767v1567H4194v2056h1806v4969c362 57 733 86 1111 86s749-30 1111-86V9167h1657z"/></g></svg> \ No newline at end of file diff --git a/web_src/svg/gitea-jetbrains.svg b/web_src/svg/gitea-jetbrains.svg new file mode 100644 index 0000000000..a7884c4289 --- /dev/null +++ b/web_src/svg/gitea-jetbrains.svg @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 70 70" style="enable-background:new 0 0 70 70;" xml:space="preserve"> +<g> + <g> + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="0.7898" y1="40.0893" x2="33.3172" y2="40.0893"> + <stop offset="0.2581" style="stop-color:#F97A12"/> + <stop offset="0.4591" style="stop-color:#B07B58"/> + <stop offset="0.7241" style="stop-color:#577BAE"/> + <stop offset="0.9105" style="stop-color:#1E7CE5"/> + <stop offset="1" style="stop-color:#087CFA"/> + </linearGradient> + <polygon style="fill:url(#SVGID_1_);" points="17.7,54.6 0.8,41.2 9.2,25.6 33.3,35 "/> + <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="25.7674" y1="24.88" x2="79.424" y2="54.57"> + <stop offset="0" style="stop-color:#F97A12"/> + <stop offset="7.179946e-002" style="stop-color:#CB7A3E"/> + <stop offset="0.1541" style="stop-color:#9E7B6A"/> + <stop offset="0.242" style="stop-color:#757B91"/> + <stop offset="0.3344" style="stop-color:#537BB1"/> + <stop offset="0.4324" style="stop-color:#387CCC"/> + <stop offset="0.5381" style="stop-color:#237CE0"/> + <stop offset="0.6552" style="stop-color:#147CEF"/> + <stop offset="0.7925" style="stop-color:#0B7CF7"/> + <stop offset="1" style="stop-color:#087CFA"/> + </linearGradient> + <polygon style="fill:url(#SVGID_2_);" points="70,18.7 68.7,59.2 41.8,70 25.6,59.6 49.3,35 38.9,12.3 48.2,1.1 "/> + <linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="63.2277" y1="42.9153" x2="48.2903" y2="-1.7191"> + <stop offset="0" style="stop-color:#FE315D"/> + <stop offset="7.840246e-002" style="stop-color:#CB417E"/> + <stop offset="0.1601" style="stop-color:#9E4E9B"/> + <stop offset="0.2474" style="stop-color:#755BB4"/> + <stop offset="0.3392" style="stop-color:#5365CA"/> + <stop offset="0.4365" style="stop-color:#386DDB"/> + <stop offset="0.5414" style="stop-color:#2374E9"/> + <stop offset="0.6576" style="stop-color:#1478F3"/> + <stop offset="0.794" style="stop-color:#0B7BF8"/> + <stop offset="1" style="stop-color:#087CFA"/> + </linearGradient> + <polygon style="fill:url(#SVGID_3_);" points="70,18.7 48.7,43.9 38.9,12.3 48.2,1.1 "/> + <linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="10.7204" y1="16.473" x2="55.5237" y2="90.58"> + <stop offset="0" style="stop-color:#FE315D"/> + <stop offset="4.023279e-002" style="stop-color:#F63462"/> + <stop offset="0.1037" style="stop-color:#DF3A71"/> + <stop offset="0.1667" style="stop-color:#C24383"/> + <stop offset="0.2912" style="stop-color:#AD4A91"/> + <stop offset="0.5498" style="stop-color:#755BB4"/> + <stop offset="0.9175" style="stop-color:#1D76ED"/> + <stop offset="1" style="stop-color:#087CFA"/> + </linearGradient> + <polygon style="fill:url(#SVGID_4_);" points="33.7,58.1 5.6,68.3 10.1,52.5 16,33.1 0,27.7 10.1,0 32.1,2.7 53.7,27.4 "/> + </g> + <g> + <rect x="13.7" y="13.5" style="fill:#000000;" width="43.2" height="43.2"/> + <rect x="17.7" y="48.6" style="fill:#FFFFFF;" width="16.2" height="2.7"/> + <polygon style="fill:#FFFFFF;" points="29.4,22.4 29.4,19.1 20.4,19.1 20.4,22.4 23,22.4 23,33.7 20.4,33.7 20.4,37 29.4,37 + 29.4,33.7 26.9,33.7 26.9,22.4 "/> + <path style="fill:#FFFFFF;" d="M38,37.3c-1.4,0-2.6-0.3-3.5-0.8c-0.9-0.5-1.7-1.2-2.3-1.9l2.5-2.8c0.5,0.6,1,1,1.5,1.3 + c0.5,0.3,1.1,0.5,1.7,0.5c0.7,0,1.3-0.2,1.8-0.7c0.4-0.5,0.6-1.2,0.6-2.3V19.1h4v11.7c0,1.1-0.1,2-0.4,2.8c-0.3,0.8-0.7,1.4-1.3,2 + c-0.5,0.5-1.2,1-2,1.2C39.8,37.1,39,37.3,38,37.3"/> + </g> +</g> +</svg> \ No newline at end of file diff --git a/web_src/svg/gitea-microsoftonline.svg b/web_src/svg/gitea-microsoftonline.svg index 72ef94eabb..eb28296419 100644 --- a/web_src/svg/gitea-microsoftonline.svg +++ b/web_src/svg/gitea-microsoftonline.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2075 2499.8" class="svg gitea-microsoftonline" width="16" height="16" aria-hidden="true"><path fill="#eb3c00" d="M0 2016.6V496.8L1344.4 0 2075 233.7v2045.9l-730.6 220.3L0 2016.6l1344.4 161.8V409.2L467.6 613.8v1198.3z"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path fill="url(#a)" d="m20.084 3.026-.224.136a8.007 8.007 0 0 0-1.009.722l.648-.456H25L26 11l-5 5-5 3.475v4.008a8 8 0 0 0 3.857 6.844l5.264 3.186L14 40h-2.145l-3.998-2.42A8 8 0 0 1 4 30.737V17.26a8 8 0 0 1 3.86-6.846l12-7.258c.074-.045.149-.089.224-.131Z"/><path fill="url(#b)" d="m20.084 3.026-.224.136a8.007 8.007 0 0 0-1.009.722l.648-.456H25L26 11l-5 5-5 3.475v4.008a8 8 0 0 0 3.857 6.844l5.264 3.186L14 40h-2.145l-3.998-2.42A8 8 0 0 1 4 30.737V17.26a8 8 0 0 1 3.86-6.846l12-7.258c.074-.045.149-.089.224-.131Z"/><path fill="url(#c)" d="M32 19v4.48a8 8 0 0 1-3.857 6.844l-12 7.264a8 8 0 0 1-8.008.16l11.722 7.096a8 8 0 0 0 8.286 0l12-7.264A8 8 0 0 0 44 30.736V27.5L43 26l-11-7Z"/><path fill="url(#d)" d="M32 19v4.48a8 8 0 0 1-3.857 6.844l-12 7.264a8 8 0 0 1-8.008.16l11.722 7.096a8 8 0 0 0 8.286 0l12-7.264A8 8 0 0 0 44 30.736V27.5L43 26l-11-7Z"/><path fill="url(#e)" d="m40.14 10.415-12-7.258a8 8 0 0 0-8.042-.139l-.238.144A8 8 0 0 0 16 10.008v9.483l3.86-2.334a8 8 0 0 1 8.28 0l12 7.258A8 8 0 0 1 43.997 31c.003-.088.004-.175.004-.263V17.26a8 8 0 0 0-3.86-6.845Z"/><path fill="url(#f)" d="m40.14 10.415-12-7.258a8 8 0 0 0-8.042-.139l-.238.144A8 8 0 0 0 16 10.008v9.483l3.86-2.334a8 8 0 0 1 8.28 0l12 7.258A8 8 0 0 1 43.997 31c.003-.088.004-.175.004-.263V17.26a8 8 0 0 0-3.86-6.845Z"/><path fill="url(#g)" d="M4.004 30.998Z"/><path fill="url(#h)" d="M4.004 30.998Z"/><defs><radialGradient id="a" cx="0" cy="0" r="1" gradientTransform="translate(17.4186 10.6383) rotate(110.528) scale(33.3657 58.1966)" gradientUnits="userSpaceOnUse"><stop offset=".064" stop-color="#AE7FE2"/><stop offset="1" stop-color="#0078D4"/></radialGradient><radialGradient id="c" cx="0" cy="0" r="1" gradientTransform="translate(10.4299 36.3511) rotate(-8.36717) scale(31.0503 20.5108)" gradientUnits="userSpaceOnUse"><stop offset=".134" stop-color="#D59DFF"/><stop offset="1" stop-color="#5E438F"/></radialGradient><radialGradient id="e" cx="0" cy="0" r="1" gradientTransform="translate(41.0552 26.504) rotate(-165.772) scale(24.9228 41.9552)" gradientUnits="userSpaceOnUse"><stop offset=".058" stop-color="#50E6FF"/><stop offset="1" stop-color="#436DCD"/></radialGradient><radialGradient id="g" cx="0" cy="0" r="1" gradientTransform="translate(41.0552 26.504) rotate(-165.772) scale(24.9228 41.9552)" gradientUnits="userSpaceOnUse"><stop offset=".058" stop-color="#50E6FF"/><stop offset="1" stop-color="#436DCD"/></radialGradient><linearGradient id="b" x1="17.512" x2="12.751" y1="37.868" y2="29.635" gradientUnits="userSpaceOnUse"><stop stop-color="#114A8B"/><stop offset="1" stop-color="#0078D4" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="40.357" x2="35.255" y1="25.377" y2="32.692" gradientUnits="userSpaceOnUse"><stop stop-color="#493474"/><stop offset="1" stop-color="#8C66BA" stop-opacity="0"/></linearGradient><linearGradient id="f" x1="16.976" x2="24.487" y1="3.057" y2="3.057" gradientUnits="userSpaceOnUse"><stop stop-color="#2D3F80"/><stop offset="1" stop-color="#436DCD" stop-opacity="0"/></linearGradient><linearGradient id="h" x1="16.976" x2="24.487" y1="3.057" y2="3.057" gradientUnits="userSpaceOnUse"><stop stop-color="#2D3F80"/><stop offset="1" stop-color="#436DCD" stop-opacity="0"/></linearGradient></defs></svg> \ No newline at end of file diff --git a/web_src/svg/gitea-vscodium.svg b/web_src/svg/gitea-vscodium.svg new file mode 100644 index 0000000000..483676fe71 --- /dev/null +++ b/web_src/svg/gitea-vscodium.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" width="100%" height="100%" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" clip-rule="evenodd" version="1.1" viewBox="0 0 16 16"><path fill-rule="nonzero" d="m10.2.2.5-.3c.3 0 .5.2.7.4l.2.8-.2 1-.8 2.4c-.3 1-.4 2 0 2.9a1046.4 1046.4 0 0 0 .8-2c.2 0 .4.1.4.3l-.3 1L9.2 13l3.1-2.9c.3-.2.7-.5.8-1a2 2 0 0 0-.3-1c-.2-.5-.5-.9-.6-1.4l.1-.7c.1-.1.3-.2.5-.1.2 0 .3.2.4.4.3.5.4 1.2.5 1.8l.6-1.2c0-.2.2-.4.4-.6l.4-.2c.2 0 .4.3.4.4v.6l-.8 1.6-1.4 1.8 1-.4c.2 0 .6.2.7.5 0 .2 0 .4-.2.5-.3.2-.6.2-1 .2-1 0-2.2.6-2.9 1.4L9.6 15c-.4.4-.9 1-1.4.8-.8-.1-.8-1.3-1-1.8 0-.3-.2-.6-.4-.7-.3-.2-.5-.3-.8-.3-.6-.1-1.2 0-1.8-.2l-.8-.4-.4-.7c-.3-.6-.3-1.2-.5-1.8A4 4 0 0 0 1 8l-.4-.4v-.4c.2-.2.5-.2.7 0 .5.2.5.8 1 1.1V6.2s.3-.1.4 0l.2.5L3 9c.4-.4.6-1 .5-1.5L3.4 7l.3-.2c.2 0 .3.2.4.3v.7c0 .6-.3 1.1-.4 1.7-.2.4-.3 1-.1 1.4.1.5.5.9.9 1 .5.3 1.1.4 1.7.4-.4-.6-.7-1.2-.7-2 0-.7.4-1.3.6-2C6.3 7 5.7 5.8 4.8 5l-1.5-.7c-.4-.2-.7-.7-.7-1.2.3-.1.7 0 1 .1L5 4.5l.6.1c.2-.3 0-.6-.2-.8-.3-.5-1-.6-1.3-1a.9.9 0 0 1-.2-.8c0-.2.3-.4.5-.4.4 0 .7.3.9.5.8.8 1.2 1.8 1.4 3 .2 1.2 0 2.5-.2 3.7 0 .3-.2.5-.1.8l.2.2c.2 0 .4 0 .5-.2.4-.3.8-.8.9-1.3l.1-1.2.1-.6.4-.2.3.3v.6c-.1.5-.2 1-.5 1.6a2 2 0 0 1-.6 1l-1 1c-.1.2-.2.6-.1.9 0 .2.2.4.4.5.4.2.8.2 1 0 .3-.1.5-.4.7-.6l.5-1.4.4-2.5C9.7 7 9.6 6 9 5.2c-.2-.4-.5-.7-1-1l-1-.8c-.2-.3-.4-.7-.3-1.2h.6c.4.1.7.4.9.8.2.4.4.8.9 1l-1-2c-.1-.3-.3-.5-.2-.8 0-.2.2-.4.4-.4s.4.1.5.3l.2.5 1 3.1a4 4 0 0 0 .4-2.3L10 1V.2Z"/></svg> \ No newline at end of file From 32c886076915ebbefcbadc934202efd17c462191 Mon Sep 17 00:00:00 2001 From: wxiaoguang <wxiaoguang@gmail.com> Date: Thu, 7 Mar 2024 10:03:41 +0800 Subject: [PATCH 28/62] Use strict protocol check when redirect (#29642) (cherry picked from commit c72e1a7abbba0cca34131a86273c987c47065dd0) --- services/context/base_test.go | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 services/context/base_test.go diff --git a/services/context/base_test.go b/services/context/base_test.go new file mode 100644 index 0000000000..823f20e00b --- /dev/null +++ b/services/context/base_test.go @@ -0,0 +1,47 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package context + +import ( + "net/http" + "net/http/httptest" + "testing" + + "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" +) + +func TestRedirect(t *testing.T) { + req, _ := http.NewRequest("GET", "/", nil) + + cases := []struct { + url string + keep bool + }{ + {"http://test", false}, + {"https://test", false}, + {"//test", false}, + {"/://test", true}, + {"/test", true}, + } + for _, c := range cases { + resp := httptest.NewRecorder() + b, cleanup := NewBaseContext(resp, req) + resp.Header().Add("Set-Cookie", (&http.Cookie{Name: setting.SessionConfig.CookieName, Value: "dummy"}).String()) + b.Redirect(c.url) + cleanup() + has := resp.Header().Get("Set-Cookie") == "i_like_gitea=dummy" + assert.Equal(t, c.keep, has, "url = %q", c.url) + } + + req, _ = http.NewRequest("GET", "/", nil) + resp := httptest.NewRecorder() + req.Header.Add("HX-Request", "true") + b, cleanup := NewBaseContext(resp, req) + b.Redirect("/other") + cleanup() + assert.Equal(t, "/other", resp.Header().Get("HX-Redirect")) + assert.Equal(t, http.StatusNoContent, resp.Code) +} From 6403d5fca7ebc00cbdacd84c88dee5f7e53f95f3 Mon Sep 17 00:00:00 2001 From: Yarden Shoham <git@yardenshoham.com> Date: Thu, 7 Mar 2024 09:28:33 +0200 Subject: [PATCH 29/62] Remove jQuery AJAX from the repo editor (#29636) # Preview Tab - Removed the jQuery AJAX call and replaced with our fetch wrapper - Tested the preview tab functionality and it works as before # Diff Tab - Removed the jQuery AJAX call and replaced with htmx - Tested the diff tab functionality and it works as before ## htmx Attributes - `hx-post="{{.RepoLink}}..."`: make a POST request to the endpoint - `hx-indicator=".tab[data-tab='diff']"`: attach the loading indicator to the tab body - `hx-target=".tab[data-tab='diff']"`: target the tab body for swapping with the response - `hx-swap="innerHTML"`: swap the target's inner HTML - `hx-include="#edit_area"`: include the value of the textarea (content) in the request body - `hx-vals='{"context":"{{.BranchLink}}"}'`: include the context in the request body - `hx-params="context,content"`: include only these keys in the request body # Demo using `fetch` and `htmx` instead of jQuery AJAX  --------- Signed-off-by: Yarden Shoham <git@yardenshoham.com> Co-authored-by: silverwind <me@silverwind.io> (cherry picked from commit c1331d1f7ab60249ed2f080b24f3e32093fa708d) --- templates/repo/editor/edit.tmpl | 4 +-- web_src/js/features/repo-editor.js | 42 ++++++++++-------------------- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl index a6dce81c08..05a8d96681 100644 --- a/templates/repo/editor/edit.tmpl +++ b/templates/repo/editor/edit.tmpl @@ -30,7 +30,7 @@ <a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.new_file"}}{{else}}{{ctx.Locale.Tr "repo.editor.edit_file"}}{{end}}</a> <a class="item" data-tab="preview" data-url="{{.Repository.Link}}/markup" data-context="{{.RepoLink}}/src/{{.BranchNameSubURL}}" data-markup-mode="file">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a> {{if not .IsNewFile}} - <a class="item" data-tab="diff" data-url="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}" data-context="{{.BranchLink}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a> + <a class="item" data-tab="diff" hx-params="context,content" hx-vals='{"context":"{{.BranchLink}}"}' hx-include="#edit_area" hx-swap="innerHTML" hx-target=".tab[data-tab='diff']" hx-indicator=".tab[data-tab='diff']" hx-post="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a> {{end}} </div> <div class="ui bottom attached active tab segment" data-tab="write"> @@ -45,7 +45,7 @@ {{ctx.Locale.Tr "loading"}} </div> <div class="ui bottom attached tab segment diff edit-diff" data-tab="diff"> - {{ctx.Locale.Tr "loading"}} + <div class="tw-p-16"></div> </div> </div> {{template "repo/editor/commit_form" .}} diff --git a/web_src/js/features/repo-editor.js b/web_src/js/features/repo-editor.js index f00f817223..4fe7ed8a4d 100644 --- a/web_src/js/features/repo-editor.js +++ b/web_src/js/features/repo-editor.js @@ -4,15 +4,14 @@ import {createCodeEditor} from './codeeditor.js'; import {hideElem, showElem} from '../utils/dom.js'; import {initMarkupContent} from '../markup/content.js'; import {attachRefIssueContextPopup} from './contextpopup.js'; - -const {csrfToken} = window.config; +import {POST} from '../modules/fetch.js'; function initEditPreviewTab($form) { const $tabMenu = $form.find('.tabular.menu'); $tabMenu.find('.item').tab(); const $previewTab = $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`); if ($previewTab.length) { - $previewTab.on('click', function () { + $previewTab.on('click', async function () { const $this = $(this); let context = `${$this.data('context')}/`; const mode = $this.data('markup-mode') || 'comment'; @@ -21,43 +20,30 @@ function initEditPreviewTab($form) { context += treePathEl.val(); } context = context.substring(0, context.lastIndexOf('/')); - $.post($this.data('url'), { - _csrf: csrfToken, - mode, - context, - text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val(), - file_path: treePathEl.val(), - }, (data) => { + + const formData = new FormData(); + formData.append('mode', mode); + formData.append('context', context); + formData.append('text', $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val()); + formData.append('file_path', treePathEl.val()); + try { + const response = await POST($this.data('url'), {data: formData}); + const data = await response.text(); const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`); renderPreviewPanelContent($previewPanel, data); - }); + } catch (error) { + console.error('Error:', error); + } }); } } -function initEditDiffTab($form) { - const $tabMenu = $form.find('.tabular.menu'); - $tabMenu.find('.item').tab(); - $tabMenu.find(`.item[data-tab="${$tabMenu.data('diff')}"]`).on('click', function () { - const $this = $(this); - $.post($this.data('url'), { - _csrf: csrfToken, - context: $this.data('context'), - content: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val(), - }, (data) => { - const $diffPreviewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('diff')}"]`); - $diffPreviewPanel.html(data); - }); - }); -} - function initEditorForm() { if ($('.repository .edit.form').length === 0) { return; } initEditPreviewTab($('.repository .edit.form')); - initEditDiffTab($('.repository .edit.form')); } function getCursorPosition($e) { From 82b7de1360870db7a8b368a3f80ede887e32e128 Mon Sep 17 00:00:00 2001 From: techknowlogick <techknowlogick@gitea.com> Date: Fri, 1 Mar 2024 03:23:28 -0500 Subject: [PATCH 30/62] Add admin API route for managing user's badges (#23106) (database migration ony) Fix #22785 --------- Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> (cherry picked from commit cb52b17f92e2d2293f7c003649743464492bca48) Conflicts: discard everything except the database migration --- models/migrations/migrations.go | 2 ++ models/migrations/v1_22/v287.go | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 models/migrations/v1_22/v287.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 1c8563cebe..93a736630e 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -560,6 +560,8 @@ var migrations = []Migration{ NewMigration("Add PreviousDuration to ActionRun", v1_22.AddPreviousDurationToActionRun), // v286 -> v287 NewMigration("Add support for SHA256 git repositories", v1_22.AdjustDBForSha256), + // v287 -> v288 + NewMigration("Use Slug instead of ID for Badges", v1_22.UseSlugInsteadOfIDForBadges), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_22/v287.go b/models/migrations/v1_22/v287.go new file mode 100644 index 0000000000..c8b1593286 --- /dev/null +++ b/models/migrations/v1_22/v287.go @@ -0,0 +1,46 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "xorm.io/xorm" +) + +type BadgeUnique struct { + ID int64 `xorm:"pk autoincr"` + Slug string `xorm:"UNIQUE"` +} + +func (BadgeUnique) TableName() string { + return "badge" +} + +func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error { + type Badge struct { + Slug string + } + + err := x.Sync(new(Badge)) + if err != nil { + return err + } + + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + _, err = sess.Exec("UPDATE `badge` SET `slug` = `id` Where `slug` IS NULL") + if err != nil { + return err + } + + err = sess.Sync(new(BadgeUnique)) + if err != nil { + return err + } + + return sess.Commit() +} From 97c8dbf3322b0bc3e05d34849f3654404d0265d3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Fri, 8 Mar 2024 00:43:32 +0800 Subject: [PATCH 31/62] Fix bug hidden on CI and make ci failed if tests failure (#29254) The tests on migration tests failed but CI reports successfully https://github.com/go-gitea/gitea/actions/runs/7364373807/job/20044685969#step:8:141 This PR will fix the bug on migration v283 and also the CI hidden behaviour. The reason is on the Makefile `GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(MIGRATE_TEST_PACKAGES)` will return the error exit code. But `for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \ GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \ done` will not work. This also fix #29602 (cherry picked from commit 45277486c2c6213b7766b1da708a991cdb1f3565) Conflicts: .github/workflows/pull-db-tests.yml Makefile models/migrations/v1_22/v283.go models/migrations/v1_22/v286_test.go models/migrations/v1_22/v287_test.go already in Forgejo for the Makefile & CI logic but Gitea changes otherwise rule --- models/migrations/base/db_test.go | 5 +- .../Test_AddIssueResourceIndexTable/issue.yml | 4 + .../attachment.yml | 11 +++ .../Test_AddRepoIDForAttachment/issue.yml | 3 + .../Test_AddRepoIDForAttachment/release.yml | 3 + .../Test_RepositoryFormat/comment.yml | 3 + .../Test_RepositoryFormat/commit_status.yml | 3 + .../Test_RepositoryFormat/pull_request.yml | 5 ++ .../Test_RepositoryFormat/release.yml | 3 + .../Test_RepositoryFormat/repo_archiver.yml | 3 + .../repo_indexer_status.yml | 3 + .../Test_RepositoryFormat/review_state.yml | 3 + .../Test_UpdateBadgeColName/badge.yml | 4 + models/migrations/v1_16/v193_test.go | 26 ++++--- models/migrations/v1_22/v283.go | 30 +++++++ models/migrations/v1_22/v286.go | 8 +- models/migrations/v1_22/v286_test.go | 78 +++++++++++-------- 17 files changed, 148 insertions(+), 47 deletions(-) create mode 100644 models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml create mode 100644 models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml create mode 100644 models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml create mode 100644 models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml create mode 100644 models/migrations/fixtures/Test_RepositoryFormat/comment.yml create mode 100644 models/migrations/fixtures/Test_RepositoryFormat/commit_status.yml create mode 100644 models/migrations/fixtures/Test_RepositoryFormat/pull_request.yml create mode 100644 models/migrations/fixtures/Test_RepositoryFormat/release.yml create mode 100644 models/migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml create mode 100644 models/migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml create mode 100644 models/migrations/fixtures/Test_RepositoryFormat/review_state.yml create mode 100644 models/migrations/fixtures/Test_UpdateBadgeColName/badge.yml diff --git a/models/migrations/base/db_test.go b/models/migrations/base/db_test.go index 4d61b758cf..80bf00b22a 100644 --- a/models/migrations/base/db_test.go +++ b/models/migrations/base/db_test.go @@ -36,12 +36,14 @@ func Test_DropTableColumns(t *testing.T) { "updated_unix", } + x.SetMapper(names.GonicMapper{}) + for i := range columns { - x.SetMapper(names.GonicMapper{}) if err := x.Sync(new(DropTest)); err != nil { t.Errorf("unable to create DropTest table: %v", err) return } + sess := x.NewSession() if err := sess.Begin(); err != nil { sess.Close() @@ -64,7 +66,6 @@ func Test_DropTableColumns(t *testing.T) { return } for j := range columns[i+1:] { - x.SetMapper(names.GonicMapper{}) if err := x.Sync(new(DropTest)); err != nil { t.Errorf("unable to create DropTest table: %v", err) return diff --git a/models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml b/models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml new file mode 100644 index 0000000000..f95d47916b --- /dev/null +++ b/models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml @@ -0,0 +1,4 @@ +- + id: 1 + repo_id: 1 + index: 1 diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml b/models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml new file mode 100644 index 0000000000..056236ba9e --- /dev/null +++ b/models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml @@ -0,0 +1,11 @@ +- + id: 1 + uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 + issue_id: 1 + release_id: 0 + +- + id: 2 + uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12 + issue_id: 0 + release_id: 1 diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml b/models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml new file mode 100644 index 0000000000..7f3255096d --- /dev/null +++ b/models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml @@ -0,0 +1,3 @@ +- + id: 1 + repo_id: 1 diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml b/models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml new file mode 100644 index 0000000000..7f3255096d --- /dev/null +++ b/models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml @@ -0,0 +1,3 @@ +- + id: 1 + repo_id: 1 diff --git a/models/migrations/fixtures/Test_RepositoryFormat/comment.yml b/models/migrations/fixtures/Test_RepositoryFormat/comment.yml new file mode 100644 index 0000000000..1197b086e3 --- /dev/null +++ b/models/migrations/fixtures/Test_RepositoryFormat/comment.yml @@ -0,0 +1,3 @@ +- + id: 1 + commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d diff --git a/models/migrations/fixtures/Test_RepositoryFormat/commit_status.yml b/models/migrations/fixtures/Test_RepositoryFormat/commit_status.yml new file mode 100644 index 0000000000..ca0aaec4cc --- /dev/null +++ b/models/migrations/fixtures/Test_RepositoryFormat/commit_status.yml @@ -0,0 +1,3 @@ +- + id: 1 + context_hash: 19fe5caf872476db265596eaac1dc35ad1c6422d diff --git a/models/migrations/fixtures/Test_RepositoryFormat/pull_request.yml b/models/migrations/fixtures/Test_RepositoryFormat/pull_request.yml new file mode 100644 index 0000000000..380cc079ee --- /dev/null +++ b/models/migrations/fixtures/Test_RepositoryFormat/pull_request.yml @@ -0,0 +1,5 @@ +- + id: 1 + commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d + merge_base: 19fe5caf872476db265596eaac1dc35ad1c6422d + merged_commit_id: 19fe5caf872476db265596eaac1dc35ad1c6422d diff --git a/models/migrations/fixtures/Test_RepositoryFormat/release.yml b/models/migrations/fixtures/Test_RepositoryFormat/release.yml new file mode 100644 index 0000000000..ffabe4ab9e --- /dev/null +++ b/models/migrations/fixtures/Test_RepositoryFormat/release.yml @@ -0,0 +1,3 @@ +- + id: 1 + sha1: 19fe5caf872476db265596eaac1dc35ad1c6422d diff --git a/models/migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml b/models/migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml new file mode 100644 index 0000000000..f04cb3b340 --- /dev/null +++ b/models/migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml @@ -0,0 +1,3 @@ +- + id: 1 + commit_id: 19fe5caf872476db265596eaac1dc35ad1c6422d diff --git a/models/migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml b/models/migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml new file mode 100644 index 0000000000..1197b086e3 --- /dev/null +++ b/models/migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml @@ -0,0 +1,3 @@ +- + id: 1 + commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d diff --git a/models/migrations/fixtures/Test_RepositoryFormat/review_state.yml b/models/migrations/fixtures/Test_RepositoryFormat/review_state.yml new file mode 100644 index 0000000000..1197b086e3 --- /dev/null +++ b/models/migrations/fixtures/Test_RepositoryFormat/review_state.yml @@ -0,0 +1,3 @@ +- + id: 1 + commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d diff --git a/models/migrations/fixtures/Test_UpdateBadgeColName/badge.yml b/models/migrations/fixtures/Test_UpdateBadgeColName/badge.yml new file mode 100644 index 0000000000..7025144106 --- /dev/null +++ b/models/migrations/fixtures/Test_UpdateBadgeColName/badge.yml @@ -0,0 +1,4 @@ +- + id: 1 + description: the badge + image_url: https://gitea.com/myimage.png diff --git a/models/migrations/v1_16/v193_test.go b/models/migrations/v1_16/v193_test.go index 17669a012e..d99bbc2962 100644 --- a/models/migrations/v1_16/v193_test.go +++ b/models/migrations/v1_16/v193_test.go @@ -15,7 +15,6 @@ func Test_AddRepoIDForAttachment(t *testing.T) { type Attachment struct { ID int64 `xorm:"pk autoincr"` UUID string `xorm:"uuid UNIQUE"` - RepoID int64 `xorm:"INDEX"` // this should not be zero IssueID int64 `xorm:"INDEX"` // maybe zero when creating ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating UploaderID int64 `xorm:"INDEX DEFAULT 0"` @@ -44,12 +43,21 @@ func Test_AddRepoIDForAttachment(t *testing.T) { return } - var issueAttachments []*Attachment - err := x.Where("issue_id > 0").Find(&issueAttachments) + type NewAttachment struct { + ID int64 `xorm:"pk autoincr"` + UUID string `xorm:"uuid UNIQUE"` + RepoID int64 `xorm:"INDEX"` // this should not be zero + IssueID int64 `xorm:"INDEX"` // maybe zero when creating + ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating + UploaderID int64 `xorm:"INDEX DEFAULT 0"` + } + + var issueAttachments []*NewAttachment + err := x.Table("attachment").Where("issue_id > 0").Find(&issueAttachments) assert.NoError(t, err) for _, attach := range issueAttachments { - assert.Greater(t, attach.RepoID, 0) - assert.Greater(t, attach.IssueID, 0) + assert.Greater(t, attach.RepoID, int64(0)) + assert.Greater(t, attach.IssueID, int64(0)) var issue Issue has, err := x.ID(attach.IssueID).Get(&issue) assert.NoError(t, err) @@ -57,12 +65,12 @@ func Test_AddRepoIDForAttachment(t *testing.T) { assert.EqualValues(t, attach.RepoID, issue.RepoID) } - var releaseAttachments []*Attachment - err = x.Where("release_id > 0").Find(&releaseAttachments) + var releaseAttachments []*NewAttachment + err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments) assert.NoError(t, err) for _, attach := range releaseAttachments { - assert.Greater(t, attach.RepoID, 0) - assert.Greater(t, attach.IssueID, 0) + assert.Greater(t, attach.RepoID, int64(0)) + assert.Greater(t, attach.ReleaseID, int64(0)) var release Release has, err := x.ID(attach.ReleaseID).Get(&release) assert.NoError(t, err) diff --git a/models/migrations/v1_22/v283.go b/models/migrations/v1_22/v283.go index 97b22f72a0..0a45c51245 100644 --- a/models/migrations/v1_22/v283.go +++ b/models/migrations/v1_22/v283.go @@ -4,10 +4,40 @@ package v1_22 //nolint import ( + "fmt" + "xorm.io/xorm" + "xorm.io/xorm/schemas" ) func AddCombinedIndexToIssueUser(x *xorm.Engine) error { + type OldIssueUser struct { + IssueID int64 + UID int64 + Cnt int64 + } + + var duplicatedIssueUsers []OldIssueUser + if err := x.SQL("select * from (select issue_id, uid, count(1) as cnt from issue_user group by issue_id, uid) a where a.cnt > 1"). + Find(&duplicatedIssueUsers); err != nil { + return err + } + for _, issueUser := range duplicatedIssueUsers { + if x.Dialect().URI().DBType == schemas.MSSQL { + if _, err := x.Exec(fmt.Sprintf("delete from issue_user where id in (SELECT top %d id FROM issue_user WHERE issue_id = ? and uid = ?)", issueUser.Cnt-1), issueUser.IssueID, issueUser.UID); err != nil { + return err + } + } else { + var ids []int64 + if err := x.SQL("SELECT id FROM issue_user WHERE issue_id = ? and uid = ? limit ?", issueUser.IssueID, issueUser.UID, issueUser.Cnt-1).Find(&ids); err != nil { + return err + } + if _, err := x.Table("issue_user").In("id", ids).Delete(); err != nil { + return err + } + } + } + type IssueUser struct { UID int64 `xorm:"INDEX unique(uid_to_issue)"` // User ID. IssueID int64 `xorm:"INDEX unique(uid_to_issue)"` diff --git a/models/migrations/v1_22/v286.go b/models/migrations/v1_22/v286.go index ef19f64221..fbbd87344f 100644 --- a/models/migrations/v1_22/v286.go +++ b/models/migrations/v1_22/v286.go @@ -36,9 +36,9 @@ func expandHashReferencesToSha256(x *xorm.Engine) error { if setting.Database.Type.IsMSSQL() { // drop indexes that need to be re-created afterwards droppedIndexes := []string{ - "DROP INDEX commit_status.IDX_commit_status_context_hash", - "DROP INDEX review_state.UQE_review_state_pull_commit_user", - "DROP INDEX repo_archiver.UQE_repo_archiver_s", + "DROP INDEX IF EXISTS [IDX_commit_status_context_hash] ON [commit_status]", + "DROP INDEX IF EXISTS [UQE_review_state_pull_commit_user] ON [review_state]", + "DROP INDEX IF EXISTS [UQE_repo_archiver_s] ON [repo_archiver]", } for _, s := range droppedIndexes { _, err := db.Exec(s) @@ -53,7 +53,7 @@ func expandHashReferencesToSha256(x *xorm.Engine) error { if setting.Database.Type.IsMySQL() { _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(64)", alts[0], alts[1])) } else if setting.Database.Type.IsMSSQL() { - _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` VARCHAR(64)", alts[0], alts[1])) + _, err = db.Exec(fmt.Sprintf("ALTER TABLE [%s] ALTER COLUMN [%s] VARCHAR(64)", alts[0], alts[1])) } else { _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` TYPE VARCHAR(64)", alts[0], alts[1])) } diff --git a/models/migrations/v1_22/v286_test.go b/models/migrations/v1_22/v286_test.go index 6493bfba2f..7c353747e3 100644 --- a/models/migrations/v1_22/v286_test.go +++ b/models/migrations/v1_22/v286_test.go @@ -14,59 +14,75 @@ import ( func PrepareOldRepository(t *testing.T) (*xorm.Engine, func()) { type Repository struct { // old struct - ID int64 `xorm:"pk autoincr"` - ObjectFormatName string `xorm:"VARCHAR(6) NOT NULL DEFAULT 'sha1'"` + ID int64 `xorm:"pk autoincr"` } - type CommitStatus struct { // old struct - ID int64 `xorm:"pk autoincr"` - ContextHash string `xorm:"char(40)"` + type CommitStatus struct { + ID int64 + ContextHash string } - type Comment struct { // old struct - ID int64 `xorm:"pk autoincr"` - CommitSHA string `xorm:"VARCHAR(40)"` + type RepoArchiver struct { + ID int64 + RepoID int64 + Type int + CommitID string } - type PullRequest struct { // old struct - ID int64 `xorm:"pk autoincr"` - MergeBase string `xorm:"VARCHAR(40)"` - MergedCommitID string `xorm:"VARCHAR(40)"` + type ReviewState struct { + ID int64 + CommitSHA string + UserID int64 + PullID int64 } - type Review struct { // old struct - ID int64 `xorm:"pk autoincr"` - CommitID string `xorm:"VARCHAR(40)"` + type Comment struct { + ID int64 + CommitSHA string } - type ReviewState struct { // old struct - ID int64 `xorm:"pk autoincr"` - CommitSHA string `xorm:"VARCHAR(40)"` + type PullRequest struct { + ID int64 + CommitSHA string + MergeBase string + MergedCommitID string } - type RepoArchiver struct { // old struct - ID int64 `xorm:"pk autoincr"` - CommitID string `xorm:"VARCHAR(40)"` + type Release struct { + ID int64 + Sha1 string } - type Release struct { // old struct - ID int64 `xorm:"pk autoincr"` - Sha1 string `xorm:"VARCHAR(40)"` + type RepoIndexerStatus struct { + ID int64 + CommitSHA string } - type RepoIndexerStatus struct { // old struct - ID int64 `xorm:"pk autoincr"` - CommitSha string `xorm:"VARCHAR(40)"` + type Review struct { + ID int64 + CommitID string } // Prepare and load the testing database - return base.PrepareTestEnv(t, 0, new(Repository), new(CommitStatus), new(Comment), new(PullRequest), new(Review), new(ReviewState), new(RepoArchiver), new(Release), new(RepoIndexerStatus)) + return base.PrepareTestEnv(t, 0, + new(Repository), + new(CommitStatus), + new(RepoArchiver), + new(ReviewState), + new(Review), + new(Comment), + new(PullRequest), + new(Release), + new(RepoIndexerStatus), + ) } func Test_RepositoryFormat(t *testing.T) { x, deferable := PrepareOldRepository(t) defer deferable() + assert.NoError(t, AdjustDBForSha256(x)) + type Repository struct { ID int64 `xorm:"pk autoincr"` ObjectFormatName string `xorg:"not null default('sha1')"` @@ -79,12 +95,10 @@ func Test_RepositoryFormat(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 4, count) - assert.NoError(t, AdjustDBForSha256(x)) - - repo.ID = 20 repo.ObjectFormatName = "sha256" _, err = x.Insert(repo) assert.NoError(t, err) + id := repo.ID count, err = x.Count(new(Repository)) assert.NoError(t, err) @@ -97,7 +111,7 @@ func Test_RepositoryFormat(t *testing.T) { assert.EqualValues(t, "sha1", repo.ObjectFormatName) repo = new(Repository) - ok, err = x.ID(20).Get(repo) + ok, err = x.ID(id).Get(repo) assert.NoError(t, err) assert.EqualValues(t, true, ok) assert.EqualValues(t, "sha256", repo.ObjectFormatName) From 8bf280a686ac750935fb45bddc47dad8ae00bd89 Mon Sep 17 00:00:00 2001 From: KN4CK3R <admin@oldschoolhack.me> Date: Mon, 4 Mar 2024 09:16:03 +0100 Subject: [PATCH 32/62] Add user blocking (#29028) (database migration ony) Fixes #17453 This PR adds the abbility to block a user from a personal account or organization to restrict how the blocked user can interact with the blocker. The docs explain what's the consequence of blocking a user. Screenshots:    --------- Co-authored-by: Lauris BH <lauris@nix.lv> (cherry picked from commit c337ff0ec70618ef2ead7850f90ab2a8458db192) --- models/migrations/migrations.go | 2 ++ models/migrations/v1_22/v288.go | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 models/migrations/v1_22/v288.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 93a736630e..6d0511ec17 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -562,6 +562,8 @@ var migrations = []Migration{ NewMigration("Add support for SHA256 git repositories", v1_22.AdjustDBForSha256), // v287 -> v288 NewMigration("Use Slug instead of ID for Badges", v1_22.UseSlugInsteadOfIDForBadges), + // v288 -> v289 + NewMigration("Add user_blocking table", v1_22.AddUserBlockingTable), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_22/v288.go b/models/migrations/v1_22/v288.go new file mode 100644 index 0000000000..7c93bfcc66 --- /dev/null +++ b/models/migrations/v1_22/v288.go @@ -0,0 +1,26 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +type Blocking struct { + ID int64 `xorm:"pk autoincr"` + BlockerID int64 `xorm:"UNIQUE(block)"` + BlockeeID int64 `xorm:"UNIQUE(block)"` + Note string + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` +} + +func (*Blocking) TableName() string { + return "user_blocking" +} + +func AddUserBlockingTable(x *xorm.Engine) error { + return x.Sync(&Blocking{}) +} From 7ea8993a0e342e7a30cb2da03216697b4819935a Mon Sep 17 00:00:00 2001 From: wxiaoguang <wxiaoguang@gmail.com> Date: Wed, 6 Mar 2024 14:26:32 +0800 Subject: [PATCH 33/62] Make wiki default branch name changable (#29603) (database migration ony) Fix #29000 Fix #28685 Fix #18568 Related: #27497 And by the way fix #24036, add a Cancel button there (one line) (cherry picked from commit 5cddab4f74bbb307ddf13e458c7ac22f93b9283a) --- models/migrations/migrations.go | 2 ++ models/migrations/v1_22/v289.go | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 models/migrations/v1_22/v289.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 6d0511ec17..447e1d1267 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -564,6 +564,8 @@ var migrations = []Migration{ NewMigration("Use Slug instead of ID for Badges", v1_22.UseSlugInsteadOfIDForBadges), // v288 -> v289 NewMigration("Add user_blocking table", v1_22.AddUserBlockingTable), + // v289 -> v290 + NewMigration("Add default_wiki_branch to repository table", v1_22.AddDefaultWikiBranch), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_22/v289.go b/models/migrations/v1_22/v289.go new file mode 100644 index 0000000000..e2dfc48715 --- /dev/null +++ b/models/migrations/v1_22/v289.go @@ -0,0 +1,18 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import "xorm.io/xorm" + +func AddDefaultWikiBranch(x *xorm.Engine) error { + type Repository struct { + ID int64 + DefaultWikiBranch string + } + if err := x.Sync(&Repository{}); err != nil { + return err + } + _, err := x.Exec("UPDATE `repository` SET default_wiki_branch = 'master' WHERE (default_wiki_branch IS NULL) OR (default_wiki_branch = '')") + return err +} From 53f6f62ad4c2eae850b95a76a6a740f430187715 Mon Sep 17 00:00:00 2001 From: oliverpool <3864879+oliverpool@users.noreply.github.com> Date: Thu, 7 Mar 2024 23:18:38 +0100 Subject: [PATCH 34/62] Store webhook event in database (#29145) Refactor the webhook logic, to have the type-dependent processing happen only in one place. --- 1. An event happens 2. It is pre-processed (depending on the webhook type) and its body is added to a task queue 3. When the task is processed, some more logic (depending on the webhook type as well) is applied to make an HTTP request This means that webhook-type dependant logic is needed in step 2 and 3. This is cumbersome and brittle to maintain. Updated webhook flow with this PR: 1. An event happens 2. It is stored as-is and added to a task queue 3. When the task is processed, the event is processed (depending on the webhook type) to make an HTTP request So the only webhook-type dependent logic happens in one place (step 3) which should be much more robust. - the raw event must be stored in the hooktask (until now, the pre-processed body was stored) - to ensure that previous hooktasks are correctly sent, a `payload_version` is added (version 1: the body has already been pre-process / version 2: the body is the raw event) So future webhook additions will only have to deal with creating an http.Request based on the raw event (no need to adjust the code in multiple places, like currently). Moreover since this processing happens when fetching from the task queue, it ensures that the queuing of new events (upon a `git push` for instance) does not get slowed down by a slow webhook. As a concrete example, the PR #19307 for custom webhooks, should be substantially smaller: - no need to change `services/webhook/deliver.go` - minimal change in `services/webhook/webhook.go` (add the new webhook to the map) - no need to change all the individual webhook files (since with this refactor the `*webhook_model.Webhook` is provided as argument) (cherry picked from commit 26653b196bd1d15c532af41f60351596dd4330bd) Conflicts: services/webhook/deliver_test.go trivial context conflict --- models/fixtures/hook_task.yml | 32 ++ models/migrations/migrations.go | 2 + models/migrations/v1_22/v290.go | 17 + models/webhook/hooktask.go | 26 +- models/webhook/webhook_test.go | 61 +-- services/webhook/deliver.go | 101 +++-- services/webhook/deliver_test.go | 202 ++++++++- services/webhook/dingtalk.go | 58 ++- services/webhook/dingtalk_test.go | 243 +++++------ services/webhook/discord.go | 68 ++- services/webhook/discord_test.go | 340 +++++++-------- services/webhook/feishu.go | 76 ++-- services/webhook/feishu_test.go | 147 +++---- services/webhook/matrix.go | 215 ++++----- services/webhook/matrix_test.go | 165 ++++--- services/webhook/msteams.go | 58 ++- services/webhook/msteams_test.go | 435 +++++++++---------- services/webhook/packagist.go | 91 ++-- services/webhook/packagist_test.go | 155 ++++--- services/webhook/payloader.go | 112 +++-- services/webhook/slack.go | 95 ++-- services/webhook/slack_test.go | 148 +++---- services/webhook/telegram.go | 62 ++- services/webhook/telegram_test.go | 147 +++---- services/webhook/webhook.go | 81 +--- services/webhook/webhook_test.go | 4 - services/webhook/wechatwork.go | 59 ++- templates/repo/settings/webhook/history.tmpl | 4 +- 28 files changed, 1686 insertions(+), 1518 deletions(-) create mode 100644 models/migrations/v1_22/v290.go diff --git a/models/fixtures/hook_task.yml b/models/fixtures/hook_task.yml index 6dbb10151a..d573406b36 100644 --- a/models/fixtures/hook_task.yml +++ b/models/fixtures/hook_task.yml @@ -3,3 +3,35 @@ hook_id: 1 uuid: uuid1 is_delivered: true + is_succeed: false + request_content: > + { + "url": "/matrix-delivered", + "http_method":"PUT", + "headers": { + "X-Head": "42" + }, + "body": "{}" + } + +- + id: 2 + hook_id: 1 + uuid: uuid2 + is_delivered: false + +- + id: 3 + hook_id: 1 + uuid: uuid3 + is_delivered: true + is_succeed: true + payload_content: '{"key":"value"}' # legacy task, payload saved in payload_content (and not in request_content) + request_content: > + { + "url": "/matrix-success", + "http_method":"PUT", + "headers": { + "X-Head": "42" + } + } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 447e1d1267..173d37234a 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -566,6 +566,8 @@ var migrations = []Migration{ NewMigration("Add user_blocking table", v1_22.AddUserBlockingTable), // v289 -> v290 NewMigration("Add default_wiki_branch to repository table", v1_22.AddDefaultWikiBranch), + // v290 -> v291 + NewMigration("Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_22/v290.go b/models/migrations/v1_22/v290.go new file mode 100644 index 0000000000..e9b7f504ba --- /dev/null +++ b/models/migrations/v1_22/v290.go @@ -0,0 +1,17 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "xorm.io/xorm" +) + +type HookTask struct { + PayloadVersion int `xorm:"DEFAULT 1"` +} + +func AddPayloadVersionToHookTaskTable(x *xorm.Engine) error { + // create missing column + return x.Sync(new(HookTask)) +} diff --git a/models/webhook/hooktask.go b/models/webhook/hooktask.go index 2fb655ebca..ff3fdbadb2 100644 --- a/models/webhook/hooktask.go +++ b/models/webhook/hooktask.go @@ -5,13 +5,13 @@ package webhook import ( "context" + "errors" "time" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -31,6 +31,7 @@ type HookRequest struct { URL string `json:"url"` HTTPMethod string `json:"http_method"` Headers map[string]string `json:"headers"` + Body string `json:"body"` } // HookResponse represents hook task response information. @@ -45,11 +46,15 @@ type HookTask struct { ID int64 `xorm:"pk autoincr"` HookID int64 `xorm:"index"` UUID string `xorm:"unique"` - api.Payloader `xorm:"-"` PayloadContent string `xorm:"LONGTEXT"` - EventType webhook_module.HookEventType - IsDelivered bool - Delivered timeutil.TimeStampNano + // PayloadVersion number to allow for smooth version upgrades: + // - PayloadVersion 1: PayloadContent contains the JSON as sent to the URL + // - PayloadVersion 2: PayloadContent contains the original event + PayloadVersion int `xorm:"DEFAULT 1"` + + EventType webhook_module.HookEventType + IsDelivered bool + Delivered timeutil.TimeStampNano // History info. IsSucceed bool @@ -115,16 +120,12 @@ func HookTasks(ctx context.Context, hookID int64, page int) ([]*HookTask, error) // it handles conversion from Payload to PayloadContent. func CreateHookTask(ctx context.Context, t *HookTask) (*HookTask, error) { t.UUID = gouuid.New().String() - if t.Payloader != nil { - data, err := t.Payloader.JSONPayload() - if err != nil { - return nil, err - } - t.PayloadContent = string(data) - } if t.Delivered == 0 { t.Delivered = timeutil.TimeStampNanoNow() } + if t.PayloadVersion == 0 { + return nil, errors.New("missing HookTask.PayloadVersion") + } return t, db.Insert(ctx, t) } @@ -165,6 +166,7 @@ func ReplayHookTask(ctx context.Context, hookID int64, uuid string) (*HookTask, HookID: task.HookID, PayloadContent: task.PayloadContent, EventType: task.EventType, + PayloadVersion: task.PayloadVersion, }) } diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go index c70c8e99fc..f4403776ce 100644 --- a/models/webhook/webhook_test.go +++ b/models/webhook/webhook_test.go @@ -12,7 +12,6 @@ import ( "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/optional" - api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -35,8 +34,10 @@ func TestWebhook_History(t *testing.T) { webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1}) tasks, err := webhook.History(db.DefaultContext, 0) assert.NoError(t, err) - if assert.Len(t, tasks, 1) { - assert.Equal(t, int64(1), tasks[0].ID) + if assert.Len(t, tasks, 3) { + assert.Equal(t, int64(3), tasks[0].ID) + assert.Equal(t, int64(2), tasks[1].ID) + assert.Equal(t, int64(1), tasks[2].ID) } webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2}) @@ -197,8 +198,10 @@ func TestHookTasks(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) hookTasks, err := HookTasks(db.DefaultContext, 1, 1) assert.NoError(t, err) - if assert.Len(t, hookTasks, 1) { - assert.Equal(t, int64(1), hookTasks[0].ID) + if assert.Len(t, hookTasks, 3) { + assert.Equal(t, int64(3), hookTasks[0].ID) + assert.Equal(t, int64(2), hookTasks[1].ID) + assert.Equal(t, int64(1), hookTasks[2].ID) } hookTasks, err = HookTasks(db.DefaultContext, unittest.NonexistentID, 1) @@ -209,8 +212,8 @@ func TestHookTasks(t *testing.T) { func TestCreateHookTask(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) hookTask := &HookTask{ - HookID: 3, - Payloader: &api.PushPayload{}, + HookID: 3, + PayloadVersion: 2, } unittest.AssertNotExistsBean(t, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask) @@ -232,10 +235,10 @@ func TestUpdateHookTask(t *testing.T) { func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) hookTask := &HookTask{ - HookID: 3, - Payloader: &api.PushPayload{}, - IsDelivered: true, - Delivered: timeutil.TimeStampNanoNow(), + HookID: 3, + IsDelivered: true, + Delivered: timeutil.TimeStampNanoNow(), + PayloadVersion: 2, } unittest.AssertNotExistsBean(t, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask) @@ -249,9 +252,9 @@ func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) { func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) hookTask := &HookTask{ - HookID: 4, - Payloader: &api.PushPayload{}, - IsDelivered: false, + HookID: 4, + IsDelivered: false, + PayloadVersion: 2, } unittest.AssertNotExistsBean(t, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask) @@ -265,10 +268,10 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) { func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) hookTask := &HookTask{ - HookID: 4, - Payloader: &api.PushPayload{}, - IsDelivered: true, - Delivered: timeutil.TimeStampNanoNow(), + HookID: 4, + IsDelivered: true, + Delivered: timeutil.TimeStampNanoNow(), + PayloadVersion: 2, } unittest.AssertNotExistsBean(t, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask) @@ -282,10 +285,10 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) { func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) hookTask := &HookTask{ - HookID: 3, - Payloader: &api.PushPayload{}, - IsDelivered: true, - Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -8).UnixNano()), + HookID: 3, + IsDelivered: true, + Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -8).UnixNano()), + PayloadVersion: 2, } unittest.AssertNotExistsBean(t, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask) @@ -299,9 +302,9 @@ func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) { func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) hookTask := &HookTask{ - HookID: 4, - Payloader: &api.PushPayload{}, - IsDelivered: false, + HookID: 4, + IsDelivered: false, + PayloadVersion: 2, } unittest.AssertNotExistsBean(t, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask) @@ -315,10 +318,10 @@ func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) { func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) hookTask := &HookTask{ - HookID: 4, - Payloader: &api.PushPayload{}, - IsDelivered: true, - Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -6).UnixNano()), + HookID: 4, + IsDelivered: true, + Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -6).UnixNano()), + PayloadVersion: 2, } unittest.AssertNotExistsBean(t, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask) diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index 16819f846a..32f1a3de45 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -32,36 +32,17 @@ import ( "github.com/gobwas/glob" ) -// Deliver deliver hook task -func Deliver(ctx context.Context, t *webhook_model.HookTask) error { - w, err := webhook_model.GetWebhookByID(ctx, t.HookID) - if err != nil { - return err - } - - defer func() { - err := recover() - if err == nil { - return - } - // There was a panic whilst delivering a hook... - log.Error("PANIC whilst trying to deliver webhook task[%d] to webhook %s Panic: %v\nStacktrace: %s", t.ID, w.URL, err, log.Stack(2)) - }() - - t.IsDelivered = true - - var req *http.Request - +func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (req *http.Request, body []byte, err error) { switch w.HTTPMethod { case "": - log.Info("HTTP Method for webhook %s empty, setting to POST as default", w.URL) + log.Info("HTTP Method for %s webhook %s [ID: %d] is not set, defaulting to POST", w.Type, w.URL, w.ID) fallthrough case http.MethodPost: switch w.ContentType { case webhook_model.ContentTypeJSON: req, err = http.NewRequest("POST", w.URL, strings.NewReader(t.PayloadContent)) if err != nil { - return err + return nil, nil, err } req.Header.Set("Content-Type", "application/json") @@ -72,50 +53,58 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error { req, err = http.NewRequest("POST", w.URL, strings.NewReader(forms.Encode())) if err != nil { - return err + return nil, nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + default: + return nil, nil, fmt.Errorf("invalid content type: %v", w.ContentType) } case http.MethodGet: u, err := url.Parse(w.URL) if err != nil { - return fmt.Errorf("unable to deliver webhook task[%d] as cannot parse webhook url %s: %w", t.ID, w.URL, err) + return nil, nil, fmt.Errorf("invalid URL: %w", err) } vals := u.Query() vals["payload"] = []string{t.PayloadContent} u.RawQuery = vals.Encode() req, err = http.NewRequest("GET", u.String(), nil) if err != nil { - return fmt.Errorf("unable to deliver webhook task[%d] as unable to create HTTP request for webhook url %s: %w", t.ID, w.URL, err) + return nil, nil, err } case http.MethodPut: switch w.Type { - case webhook_module.MATRIX: + case webhook_module.MATRIX: // used when t.Version == 1 txnID, err := getMatrixTxnID([]byte(t.PayloadContent)) if err != nil { - return err + return nil, nil, err } url := fmt.Sprintf("%s/%s", w.URL, url.PathEscape(txnID)) req, err = http.NewRequest("PUT", url, strings.NewReader(t.PayloadContent)) if err != nil { - return fmt.Errorf("unable to deliver webhook task[%d] as cannot create matrix request for webhook url %s: %w", t.ID, w.URL, err) + return nil, nil, err } default: - return fmt.Errorf("invalid http method for webhook task[%d] in webhook %s: %v", t.ID, w.URL, w.HTTPMethod) + return nil, nil, fmt.Errorf("invalid http method: %v", w.HTTPMethod) } default: - return fmt.Errorf("invalid http method for webhook task[%d] in webhook %s: %v", t.ID, w.URL, w.HTTPMethod) + return nil, nil, fmt.Errorf("invalid http method: %v", w.HTTPMethod) } + body = []byte(t.PayloadContent) + return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body) +} + +func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTask, payloadContent []byte) error { var signatureSHA1 string var signatureSHA256 string - if len(w.Secret) > 0 { - sig1 := hmac.New(sha1.New, []byte(w.Secret)) - sig256 := hmac.New(sha256.New, []byte(w.Secret)) - _, err = io.MultiWriter(sig1, sig256).Write([]byte(t.PayloadContent)) + if len(secret) > 0 { + sig1 := hmac.New(sha1.New, secret) + sig256 := hmac.New(sha256.New, secret) + _, err := io.MultiWriter(sig1, sig256).Write(payloadContent) if err != nil { - log.Error("prepareWebhooks.sigWrite: %v", err) + // this error should never happen, since the hashes are writing to []byte and always return a nil error. + return fmt.Errorf("prepareWebhooks.sigWrite: %w", err) } signatureSHA1 = hex.EncodeToString(sig1.Sum(nil)) signatureSHA256 = hex.EncodeToString(sig256.Sum(nil)) @@ -140,15 +129,36 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error { req.Header["X-GitHub-Delivery"] = []string{t.UUID} req.Header["X-GitHub-Event"] = []string{event} req.Header["X-GitHub-Event-Type"] = []string{eventType} + return nil +} - // Add Authorization Header - authorization, err := w.HeaderAuthorization() +// Deliver creates the [http.Request] (depending on the webhook type), sends it +// and records the status and response. +func Deliver(ctx context.Context, t *webhook_model.HookTask) error { + w, err := webhook_model.GetWebhookByID(ctx, t.HookID) if err != nil { - log.Error("Webhook could not get Authorization header [%d]: %v", w.ID, err) return err } - if authorization != "" { - req.Header["Authorization"] = []string{authorization} + + defer func() { + err := recover() + if err == nil { + return + } + // There was a panic whilst delivering a hook... + log.Error("PANIC whilst trying to deliver webhook task[%d] to webhook %s Panic: %v\nStacktrace: %s", t.ID, w.URL, err, log.Stack(2)) + }() + + t.IsDelivered = true + + newRequest := webhookRequesters[w.Type] + if t.PayloadVersion == 1 || newRequest == nil { + newRequest = newDefaultRequest + } + + req, body, err := newRequest(ctx, w, t) + if err != nil { + return fmt.Errorf("cannot create http request for webhook %s[%d %s]: %w", w.Type, w.ID, w.URL, err) } // Record delivery information. @@ -156,11 +166,22 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error { URL: req.URL.String(), HTTPMethod: req.Method, Headers: map[string]string{}, + Body: string(body), } for k, vals := range req.Header { t.RequestInfo.Headers[k] = strings.Join(vals, ",") } + // Add Authorization Header + authorization, err := w.HeaderAuthorization() + if err != nil { + return fmt.Errorf("cannot get Authorization header for webhook %s[%d %s]: %w", w.Type, w.ID, w.URL, err) + } + if authorization != "" { + req.Header.Set("Authorization", authorization) + t.RequestInfo.Headers["Authorization"] = "******" + } + t.ResponseInfo = &webhook_model.HookResponse{ Headers: map[string]string{}, } diff --git a/services/webhook/deliver_test.go b/services/webhook/deliver_test.go index eca2ba244b..3324c7f0e3 100644 --- a/services/webhook/deliver_test.go +++ b/services/webhook/deliver_test.go @@ -5,10 +5,12 @@ package webhook import ( "context" + "io" "net/http" "net/http/httptest" "net/url" "os" + "strings" "testing" "time" @@ -17,7 +19,6 @@ import ( webhook_model "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/hostmatcher" "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" "github.com/stretchr/testify/assert" @@ -114,13 +115,15 @@ func TestWebhookDeliverAuthorizationHeader(t *testing.T) { assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook)) db.GetEngine(db.DefaultContext).NoAutoTime().DB().Logger.ShowSQL(true) - hookTask := &webhook_model.HookTask{HookID: hook.ID, EventType: webhook_module.HookEventPush, Payloader: &api.PushPayload{}} + hookTask := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadVersion: 2, + } hookTask, err = webhook_model.CreateHookTask(db.DefaultContext, hookTask) assert.NoError(t, err) - if !assert.NotNil(t, hookTask) { - return - } + assert.NotNil(t, hookTask) assert.NoError(t, Deliver(context.Background(), hookTask)) select { @@ -130,4 +133,193 @@ func TestWebhookDeliverAuthorizationHeader(t *testing.T) { } assert.True(t, hookTask.IsSucceed) + assert.Equal(t, "******", hookTask.RequestInfo.Headers["Authorization"]) +} + +func TestWebhookDeliverHookTask(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + done := make(chan struct{}, 1) + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "PUT", r.Method) + switch r.URL.Path { + case "/webhook/66d222a5d6349e1311f551e50722d837e30fce98": + // Version 1 + assert.Equal(t, "push", r.Header.Get("X-GitHub-Event")) + assert.Equal(t, "", r.Header.Get("Content-Type")) + body, err := io.ReadAll(r.Body) + assert.NoError(t, err) + assert.Equal(t, `{"data": 42}`, string(body)) + + case "/webhook/6db5dc1e282529a8c162c7fe93dd2667494eeb51": + // Version 2 + assert.Equal(t, "push", r.Header.Get("X-GitHub-Event")) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + body, err := io.ReadAll(r.Body) + assert.NoError(t, err) + assert.Len(t, body, 2147) + + default: + w.WriteHeader(404) + t.Fatalf("unexpected url path %s", r.URL.Path) + return + } + w.WriteHeader(200) + done <- struct{}{} + })) + t.Cleanup(s.Close) + + hook := &webhook_model.Webhook{ + RepoID: 3, + IsActive: true, + Type: webhook_module.MATRIX, + URL: s.URL + "/webhook", + HTTPMethod: "PUT", + ContentType: webhook_model.ContentTypeJSON, + Meta: `{"message_type":0}`, // text + } + assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook)) + + t.Run("Version 1", func(t *testing.T) { + hookTask := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadContent: `{"data": 42}`, + PayloadVersion: 1, + } + + hookTask, err := webhook_model.CreateHookTask(db.DefaultContext, hookTask) + assert.NoError(t, err) + assert.NotNil(t, hookTask) + + assert.NoError(t, Deliver(context.Background(), hookTask)) + select { + case <-done: + case <-time.After(5 * time.Second): + t.Fatal("waited to long for request to happen") + } + + assert.True(t, hookTask.IsSucceed) + }) + + t.Run("Version 2", func(t *testing.T) { + p := pushTestPayload() + data, err := p.JSONPayload() + assert.NoError(t, err) + + hookTask := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadContent: string(data), + PayloadVersion: 2, + } + + hookTask, err = webhook_model.CreateHookTask(db.DefaultContext, hookTask) + assert.NoError(t, err) + assert.NotNil(t, hookTask) + + assert.NoError(t, Deliver(context.Background(), hookTask)) + select { + case <-done: + case <-time.After(5 * time.Second): + t.Fatal("waited to long for request to happen") + } + + assert.True(t, hookTask.IsSucceed) + }) +} + +func TestWebhookDeliverSpecificTypes(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + type hookCase struct { + gotBody chan []byte + } + + cases := map[string]hookCase{ + webhook_module.SLACK: { + gotBody: make(chan []byte, 1), + }, + webhook_module.DISCORD: { + gotBody: make(chan []byte, 1), + }, + webhook_module.DINGTALK: { + gotBody: make(chan []byte, 1), + }, + webhook_module.TELEGRAM: { + gotBody: make(chan []byte, 1), + }, + webhook_module.MSTEAMS: { + gotBody: make(chan []byte, 1), + }, + webhook_module.FEISHU: { + gotBody: make(chan []byte, 1), + }, + webhook_module.MATRIX: { + gotBody: make(chan []byte, 1), + }, + webhook_module.WECHATWORK: { + gotBody: make(chan []byte, 1), + }, + webhook_module.PACKAGIST: { + gotBody: make(chan []byte, 1), + }, + } + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "application/json", r.Header.Get("Content-Type"), r.URL.Path) + + typ := strings.Split(r.URL.Path, "/")[1] // take first segment (after skipping leading slash) + hc := cases[typ] + require.NotNil(t, hc.gotBody, r.URL.Path) + body, err := io.ReadAll(r.Body) + assert.NoError(t, err) + w.WriteHeader(200) + hc.gotBody <- body + })) + t.Cleanup(s.Close) + + p := pushTestPayload() + data, err := p.JSONPayload() + assert.NoError(t, err) + + for typ, hc := range cases { + typ := typ + hc := hc + t.Run(typ, func(t *testing.T) { + t.Parallel() + hook := &webhook_model.Webhook{ + RepoID: 3, + IsActive: true, + Type: typ, + URL: s.URL + "/" + typ, + HTTPMethod: "POST", + ContentType: 0, // set to 0 so that falling back to default request fails with "invalid content type" + Meta: "{}", + } + assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook)) + + hookTask := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadContent: string(data), + PayloadVersion: 2, + } + + hookTask, err := webhook_model.CreateHookTask(db.DefaultContext, hookTask) + assert.NoError(t, err) + assert.NotNil(t, hookTask) + + assert.NoError(t, Deliver(context.Background(), hookTask)) + select { + case gotBody := <-hc.gotBody: + assert.NotEqual(t, string(data), string(gotBody), "request body must be different from the event payload") + assert.Equal(t, hookTask.RequestInfo.Body, string(gotBody), "request body was not saved") + case <-time.After(5 * time.Second): + t.Fatal("waited to long for request to happen") + } + + assert.True(t, hookTask.IsSucceed) + }) + } } diff --git a/services/webhook/dingtalk.go b/services/webhook/dingtalk.go index d615e7254f..c57d04415a 100644 --- a/services/webhook/dingtalk.go +++ b/services/webhook/dingtalk.go @@ -4,12 +4,14 @@ package webhook import ( + "context" "fmt" + "net/http" "net/url" "strings" + webhook_model "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -22,19 +24,8 @@ type ( DingtalkPayload dingtalk.Payload ) -var _ PayloadConvertor = &DingtalkPayload{} - -// JSONPayload Marshals the DingtalkPayload to json -func (d *DingtalkPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(d, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - // Create implements PayloadConvertor Create method -func (d *DingtalkPayload) Create(p *api.CreatePayload) (api.Payloader, error) { +func (dc dingtalkConvertor) Create(p *api.CreatePayload) (DingtalkPayload, error) { // created tag/branch refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) @@ -43,7 +34,7 @@ func (d *DingtalkPayload) Create(p *api.CreatePayload) (api.Payloader, error) { } // Delete implements PayloadConvertor Delete method -func (d *DingtalkPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { +func (dc dingtalkConvertor) Delete(p *api.DeletePayload) (DingtalkPayload, error) { // created tag/branch refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) @@ -52,14 +43,14 @@ func (d *DingtalkPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { } // Fork implements PayloadConvertor Fork method -func (d *DingtalkPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { +func (dc dingtalkConvertor) Fork(p *api.ForkPayload) (DingtalkPayload, error) { title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) return createDingtalkPayload(title, title, fmt.Sprintf("view forked repo %s", p.Repo.FullName), p.Repo.HTMLURL), nil } // Push implements PayloadConvertor Push method -func (d *DingtalkPayload) Push(p *api.PushPayload) (api.Payloader, error) { +func (dc dingtalkConvertor) Push(p *api.PushPayload) (DingtalkPayload, error) { var ( branchName = git.RefName(p.Ref).ShortName() commitDesc string @@ -100,14 +91,14 @@ func (d *DingtalkPayload) Push(p *api.PushPayload) (api.Payloader, error) { } // Issue implements PayloadConvertor Issue method -func (d *DingtalkPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { +func (dc dingtalkConvertor) Issue(p *api.IssuePayload) (DingtalkPayload, error) { text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true) return createDingtalkPayload(issueTitle, text+"\r\n\r\n"+attachmentText, "view issue", p.Issue.HTMLURL), nil } // Wiki implements PayloadConvertor Wiki method -func (d *DingtalkPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) { +func (dc dingtalkConvertor) Wiki(p *api.WikiPayload) (DingtalkPayload, error) { text, _, _ := getWikiPayloadInfo(p, noneLinkFormatter, true) url := p.Repository.HTMLURL + "/wiki/" + url.PathEscape(p.Page) @@ -115,27 +106,27 @@ func (d *DingtalkPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) { } // IssueComment implements PayloadConvertor IssueComment method -func (d *DingtalkPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { +func (dc dingtalkConvertor) IssueComment(p *api.IssueCommentPayload) (DingtalkPayload, error) { text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true) return createDingtalkPayload(issueTitle, text+"\r\n\r\n"+p.Comment.Body, "view issue comment", p.Comment.HTMLURL), nil } // PullRequest implements PayloadConvertor PullRequest method -func (d *DingtalkPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { +func (dc dingtalkConvertor) PullRequest(p *api.PullRequestPayload) (DingtalkPayload, error) { text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true) return createDingtalkPayload(issueTitle, text+"\r\n\r\n"+attachmentText, "view pull request", p.PullRequest.HTMLURL), nil } // Review implements PayloadConvertor Review method -func (d *DingtalkPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) { +func (dc dingtalkConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (DingtalkPayload, error) { var text, title string switch p.Action { case api.HookIssueReviewed: action, err := parseHookPullRequestEventType(event) if err != nil { - return nil, err + return DingtalkPayload{}, err } title = fmt.Sprintf("[%s] Pull request review %s : #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title) @@ -146,14 +137,14 @@ func (d *DingtalkPayload) Review(p *api.PullRequestPayload, event webhook_module } // Repository implements PayloadConvertor Repository method -func (d *DingtalkPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { +func (dc dingtalkConvertor) Repository(p *api.RepositoryPayload) (DingtalkPayload, error) { switch p.Action { case api.HookRepoCreated: title := fmt.Sprintf("[%s] Repository created", p.Repository.FullName) return createDingtalkPayload(title, title, "view repository", p.Repository.HTMLURL), nil case api.HookRepoDeleted: title := fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName) - return &DingtalkPayload{ + return DingtalkPayload{ MsgType: "text", Text: struct { Content string `json:"content"` @@ -163,24 +154,24 @@ func (d *DingtalkPayload) Repository(p *api.RepositoryPayload) (api.Payloader, e }, nil } - return nil, nil + return DingtalkPayload{}, nil } // Release implements PayloadConvertor Release method -func (d *DingtalkPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { +func (dc dingtalkConvertor) Release(p *api.ReleasePayload) (DingtalkPayload, error) { text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true) return createDingtalkPayload(text, text, "view release", p.Release.HTMLURL), nil } -func (d *DingtalkPayload) Package(p *api.PackagePayload) (api.Payloader, error) { +func (dc dingtalkConvertor) Package(p *api.PackagePayload) (DingtalkPayload, error) { text, _ := getPackagePayloadInfo(p, noneLinkFormatter, true) return createDingtalkPayload(text, text, "view package", p.Package.HTMLURL), nil } -func createDingtalkPayload(title, text, singleTitle, singleURL string) *DingtalkPayload { - return &DingtalkPayload{ +func createDingtalkPayload(title, text, singleTitle, singleURL string) DingtalkPayload { + return DingtalkPayload{ MsgType: "actionCard", ActionCard: dingtalk.ActionCard{ Text: strings.TrimSpace(text), @@ -195,7 +186,10 @@ func createDingtalkPayload(title, text, singleTitle, singleURL string) *Dingtalk } } -// GetDingtalkPayload converts a ding talk webhook into a DingtalkPayload -func GetDingtalkPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) { - return convertPayloader(new(DingtalkPayload), p, event) +type dingtalkConvertor struct{} + +var _ payloadConvertor[DingtalkPayload] = dingtalkConvertor{} + +func newDingtalkRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { + return newJSONRequest(dingtalkConvertor{}, w, t, true) } diff --git a/services/webhook/dingtalk_test.go b/services/webhook/dingtalk_test.go index a03fa46f14..25f47347d0 100644 --- a/services/webhook/dingtalk_test.go +++ b/services/webhook/dingtalk_test.go @@ -4,9 +4,12 @@ package webhook import ( + "context" "net/url" "testing" + webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -24,248 +27,226 @@ func TestDingTalkPayload(t *testing.T) { } return "" } + dc := dingtalkConvertor{} t.Run("Create", func(t *testing.T) { p := createTestPayload() - d := new(DingtalkPayload) - pl, err := d.Create(p) + pl, err := dc.Create(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[test/repo] branch test created", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "[test/repo] branch test created", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view ref test", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[test/repo] branch test created", pl.ActionCard.Text) + assert.Equal(t, "[test/repo] branch test created", pl.ActionCard.Title) + assert.Equal(t, "view ref test", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.ActionCard.SingleURL)) }) t.Run("Delete", func(t *testing.T) { p := deleteTestPayload() - d := new(DingtalkPayload) - pl, err := d.Delete(p) + pl, err := dc.Delete(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[test/repo] branch test deleted", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "[test/repo] branch test deleted", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view ref test", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[test/repo] branch test deleted", pl.ActionCard.Text) + assert.Equal(t, "[test/repo] branch test deleted", pl.ActionCard.Title) + assert.Equal(t, "view ref test", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.ActionCard.SingleURL)) }) t.Run("Fork", func(t *testing.T) { p := forkTestPayload() - d := new(DingtalkPayload) - pl, err := d.Fork(p) + pl, err := dc.Fork(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "test/repo2 is forked to test/repo", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "test/repo2 is forked to test/repo", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view forked repo test/repo", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "test/repo2 is forked to test/repo", pl.ActionCard.Text) + assert.Equal(t, "test/repo2 is forked to test/repo", pl.ActionCard.Title) + assert.Equal(t, "view forked repo test/repo", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.ActionCard.SingleURL)) }) t.Run("Push", func(t *testing.T) { p := pushTestPayload() - d := new(DingtalkPayload) - pl, err := d.Push(p) + pl, err := dc.Push(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "[test/repo:test] 2 new commits", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view commits", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.ActionCard.Text) + assert.Equal(t, "[test/repo:test] 2 new commits", pl.ActionCard.Title) + assert.Equal(t, "view commits", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.ActionCard.SingleURL)) }) t.Run("Issue", func(t *testing.T) { p := issueTestPayload() - d := new(DingtalkPayload) p.Action = api.HookIssueOpened - pl, err := d.Issue(p) + pl, err := dc.Issue(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[test/repo] Issue opened: #2 crash by user1\r\n\r\nissue body", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "#2 crash", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view issue", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[test/repo] Issue opened: #2 crash by user1\r\n\r\nissue body", pl.ActionCard.Text) + assert.Equal(t, "#2 crash", pl.ActionCard.Title) + assert.Equal(t, "view issue", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo/issues/2", parseRealSingleURL(pl.ActionCard.SingleURL)) p.Action = api.HookIssueClosed - pl, err = d.Issue(p) + pl, err = dc.Issue(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[test/repo] Issue closed: #2 crash by user1", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "#2 crash", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view issue", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[test/repo] Issue closed: #2 crash by user1", pl.ActionCard.Text) + assert.Equal(t, "#2 crash", pl.ActionCard.Title) + assert.Equal(t, "view issue", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo/issues/2", parseRealSingleURL(pl.ActionCard.SingleURL)) }) t.Run("IssueComment", func(t *testing.T) { p := issueCommentTestPayload() - d := new(DingtalkPayload) - pl, err := d.IssueComment(p) + pl, err := dc.IssueComment(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[test/repo] New comment on issue #2 crash by user1\r\n\r\nmore info needed", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "#2 crash", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view issue comment", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[test/repo] New comment on issue #2 crash by user1\r\n\r\nmore info needed", pl.ActionCard.Text) + assert.Equal(t, "#2 crash", pl.ActionCard.Title) + assert.Equal(t, "view issue comment", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", parseRealSingleURL(pl.ActionCard.SingleURL)) }) t.Run("PullRequest", func(t *testing.T) { p := pullRequestTestPayload() - d := new(DingtalkPayload) - pl, err := d.PullRequest(p) + pl, err := dc.PullRequest(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug by user1\r\n\r\nfixes bug #2", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "#12 Fix bug", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view pull request", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug by user1\r\n\r\nfixes bug #2", pl.ActionCard.Text) + assert.Equal(t, "#12 Fix bug", pl.ActionCard.Title) + assert.Equal(t, "view pull request", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", parseRealSingleURL(pl.ActionCard.SingleURL)) }) t.Run("PullRequestComment", func(t *testing.T) { p := pullRequestCommentTestPayload() - d := new(DingtalkPayload) - pl, err := d.IssueComment(p) + pl, err := dc.IssueComment(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug by user1\r\n\r\nchanges requested", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "#12 Fix bug", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view issue comment", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug by user1\r\n\r\nchanges requested", pl.ActionCard.Text) + assert.Equal(t, "#12 Fix bug", pl.ActionCard.Title) + assert.Equal(t, "view issue comment", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", parseRealSingleURL(pl.ActionCard.SingleURL)) }) t.Run("Review", func(t *testing.T) { p := pullRequestTestPayload() p.Action = api.HookIssueReviewed - d := new(DingtalkPayload) - pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved) + pl, err := dc.Review(p, webhook_module.HookEventPullRequestReviewApproved) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug\r\n\r\ngood job", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view pull request", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug\r\n\r\ngood job", pl.ActionCard.Text) + assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug", pl.ActionCard.Title) + assert.Equal(t, "view pull request", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", parseRealSingleURL(pl.ActionCard.SingleURL)) }) t.Run("Repository", func(t *testing.T) { p := repositoryTestPayload() - d := new(DingtalkPayload) - pl, err := d.Repository(p) + pl, err := dc.Repository(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[test/repo] Repository created", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "[test/repo] Repository created", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view repository", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[test/repo] Repository created", pl.ActionCard.Text) + assert.Equal(t, "[test/repo] Repository created", pl.ActionCard.Title) + assert.Equal(t, "view repository", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.ActionCard.SingleURL)) }) t.Run("Package", func(t *testing.T) { p := packageTestPayload() - d := new(DingtalkPayload) - pl, err := d.Package(p) + pl, err := dc.Package(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view package", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.ActionCard.Text) + assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.ActionCard.Title) + assert.Equal(t, "view package", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", parseRealSingleURL(pl.ActionCard.SingleURL)) }) t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() - d := new(DingtalkPayload) p.Action = api.HookWikiCreated - pl, err := d.Wiki(p) + pl, err := dc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view wiki", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.ActionCard.Text) + assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.ActionCard.Title) + assert.Equal(t, "view wiki", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.ActionCard.SingleURL)) p.Action = api.HookWikiEdited - pl, err = d.Wiki(p) + pl, err = dc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view wiki", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.ActionCard.Text) + assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.ActionCard.Title) + assert.Equal(t, "view wiki", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.ActionCard.SingleURL)) p.Action = api.HookWikiDeleted - pl, err = d.Wiki(p) + pl, err = dc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view wiki", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.ActionCard.Text) + assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.ActionCard.Title) + assert.Equal(t, "view wiki", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.ActionCard.SingleURL)) }) t.Run("Release", func(t *testing.T) { p := pullReleaseTestPayload() - d := new(DingtalkPayload) - pl, err := d.Release(p) + pl, err := dc.Release(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.(*DingtalkPayload).ActionCard.Text) - assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "view release", pl.(*DingtalkPayload).ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL)) + assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.ActionCard.Text) + assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.ActionCard.Title) + assert.Equal(t, "view release", pl.ActionCard.SingleTitle) + assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", parseRealSingleURL(pl.ActionCard.SingleURL)) }) } func TestDingTalkJSONPayload(t *testing.T) { p := pushTestPayload() - - pl, err := new(DingtalkPayload).Push(p) + data, err := p.JSONPayload() require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DingtalkPayload{}, pl) - json, err := pl.JSONPayload() + hook := &webhook_model.Webhook{ + RepoID: 3, + IsActive: true, + Type: webhook_module.DINGTALK, + URL: "https://dingtalk.example.com/", + Meta: ``, + HTTPMethod: "POST", + } + task := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadContent: string(data), + PayloadVersion: 2, + } + + req, reqBody, err := newDingtalkRequest(context.Background(), hook, task) + require.NotNil(t, req) + require.NotNil(t, reqBody) require.NoError(t, err) - assert.NotEmpty(t, json) + + assert.Equal(t, "POST", req.Method) + assert.Equal(t, "https://dingtalk.example.com/", req.URL.String()) + assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) + assert.Equal(t, "application/json", req.Header.Get("Content-Type")) + var body DingtalkPayload + err = json.NewDecoder(req.Body).Decode(&body) + assert.NoError(t, err) + assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", body.ActionCard.Text) } diff --git a/services/webhook/discord.go b/services/webhook/discord.go index e2ac1410b8..659754d5e0 100644 --- a/services/webhook/discord.go +++ b/services/webhook/discord.go @@ -4,8 +4,10 @@ package webhook import ( + "context" "errors" "fmt" + "net/http" "net/url" "strconv" "strings" @@ -98,19 +100,8 @@ var ( redColor = color("ff3232") ) -// JSONPayload Marshals the DiscordPayload to json -func (d *DiscordPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(d, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -var _ PayloadConvertor = &DiscordPayload{} - // Create implements PayloadConvertor Create method -func (d *DiscordPayload) Create(p *api.CreatePayload) (api.Payloader, error) { +func (d discordConvertor) Create(p *api.CreatePayload) (DiscordPayload, error) { // created tag/branch refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) @@ -119,7 +110,7 @@ func (d *DiscordPayload) Create(p *api.CreatePayload) (api.Payloader, error) { } // Delete implements PayloadConvertor Delete method -func (d *DiscordPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { +func (d discordConvertor) Delete(p *api.DeletePayload) (DiscordPayload, error) { // deleted tag/branch refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) @@ -128,14 +119,14 @@ func (d *DiscordPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { } // Fork implements PayloadConvertor Fork method -func (d *DiscordPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { +func (d discordConvertor) Fork(p *api.ForkPayload) (DiscordPayload, error) { title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) return d.createPayload(p.Sender, title, "", p.Repo.HTMLURL, greenColor), nil } // Push implements PayloadConvertor Push method -func (d *DiscordPayload) Push(p *api.PushPayload) (api.Payloader, error) { +func (d discordConvertor) Push(p *api.PushPayload) (DiscordPayload, error) { var ( branchName = git.RefName(p.Ref).ShortName() commitDesc string @@ -170,35 +161,35 @@ func (d *DiscordPayload) Push(p *api.PushPayload) (api.Payloader, error) { } // Issue implements PayloadConvertor Issue method -func (d *DiscordPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { +func (d discordConvertor) Issue(p *api.IssuePayload) (DiscordPayload, error) { title, _, text, color := getIssuesPayloadInfo(p, noneLinkFormatter, false) return d.createPayload(p.Sender, title, text, p.Issue.HTMLURL, color), nil } // IssueComment implements PayloadConvertor IssueComment method -func (d *DiscordPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { +func (d discordConvertor) IssueComment(p *api.IssueCommentPayload) (DiscordPayload, error) { title, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false) return d.createPayload(p.Sender, title, p.Comment.Body, p.Comment.HTMLURL, color), nil } // PullRequest implements PayloadConvertor PullRequest method -func (d *DiscordPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { +func (d discordConvertor) PullRequest(p *api.PullRequestPayload) (DiscordPayload, error) { title, _, text, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false) return d.createPayload(p.Sender, title, text, p.PullRequest.HTMLURL, color), nil } // Review implements PayloadConvertor Review method -func (d *DiscordPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) { +func (d discordConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (DiscordPayload, error) { var text, title string var color int switch p.Action { case api.HookIssueReviewed: action, err := parseHookPullRequestEventType(event) if err != nil { - return nil, err + return DiscordPayload{}, err } title = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title) @@ -220,7 +211,7 @@ func (d *DiscordPayload) Review(p *api.PullRequestPayload, event webhook_module. } // Repository implements PayloadConvertor Repository method -func (d *DiscordPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { +func (d discordConvertor) Repository(p *api.RepositoryPayload) (DiscordPayload, error) { var title, url string var color int switch p.Action { @@ -237,7 +228,7 @@ func (d *DiscordPayload) Repository(p *api.RepositoryPayload) (api.Payloader, er } // Wiki implements PayloadConvertor Wiki method -func (d *DiscordPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) { +func (d discordConvertor) Wiki(p *api.WikiPayload) (DiscordPayload, error) { text, color, _ := getWikiPayloadInfo(p, noneLinkFormatter, false) htmlLink := p.Repository.HTMLURL + "/wiki/" + url.PathEscape(p.Page) @@ -250,30 +241,35 @@ func (d *DiscordPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) { } // Release implements PayloadConvertor Release method -func (d *DiscordPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { +func (d discordConvertor) Release(p *api.ReleasePayload) (DiscordPayload, error) { text, color := getReleasePayloadInfo(p, noneLinkFormatter, false) return d.createPayload(p.Sender, text, p.Release.Note, p.Release.HTMLURL, color), nil } -func (d *DiscordPayload) Package(p *api.PackagePayload) (api.Payloader, error) { +func (d discordConvertor) Package(p *api.PackagePayload) (DiscordPayload, error) { text, color := getPackagePayloadInfo(p, noneLinkFormatter, false) return d.createPayload(p.Sender, text, "", p.Package.HTMLURL, color), nil } -// GetDiscordPayload converts a discord webhook into a DiscordPayload -func GetDiscordPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) { - s := new(DiscordPayload) +type discordConvertor struct { + Username string + AvatarURL string +} - discord := &DiscordMeta{} - if err := json.Unmarshal([]byte(meta), &discord); err != nil { - return s, errors.New("GetDiscordPayload meta json:" + err.Error()) +var _ payloadConvertor[DiscordPayload] = discordConvertor{} + +func newDiscordRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { + meta := &DiscordMeta{} + if err := json.Unmarshal([]byte(w.Meta), meta); err != nil { + return nil, nil, fmt.Errorf("newDiscordRequest meta json: %w", err) } - s.Username = discord.Username - s.AvatarURL = discord.IconURL - - return convertPayloader(s, p, event) + sc := discordConvertor{ + Username: meta.Username, + AvatarURL: meta.IconURL, + } + return newJSONRequest(sc, w, t, true) } func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, error) { @@ -291,8 +287,8 @@ func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, } } -func (d *DiscordPayload) createPayload(s *api.User, title, text, url string, color int) *DiscordPayload { - return &DiscordPayload{ +func (d discordConvertor) createPayload(s *api.User, title, text, url string, color int) DiscordPayload { + return DiscordPayload{ Username: d.Username, AvatarURL: d.AvatarURL, Embeds: []DiscordEmbed{ diff --git a/services/webhook/discord_test.go b/services/webhook/discord_test.go index b567cbc395..c04b95383b 100644 --- a/services/webhook/discord_test.go +++ b/services/webhook/discord_test.go @@ -4,8 +4,11 @@ package webhook import ( + "context" "testing" + webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -15,295 +18,274 @@ import ( ) func TestDiscordPayload(t *testing.T) { + dc := discordConvertor{} + t.Run("Create", func(t *testing.T) { p := createTestPayload() - d := new(DiscordPayload) - pl, err := d.Create(p) + pl, err := dc.Create(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo] branch test created", pl.(*DiscordPayload).Embeds[0].Title) - assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo] branch test created", pl.Embeds[0].Title) + assert.Empty(t, pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) t.Run("Delete", func(t *testing.T) { p := deleteTestPayload() - d := new(DiscordPayload) - pl, err := d.Delete(p) + pl, err := dc.Delete(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo] branch test deleted", pl.(*DiscordPayload).Embeds[0].Title) - assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo] branch test deleted", pl.Embeds[0].Title) + assert.Empty(t, pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) t.Run("Fork", func(t *testing.T) { p := forkTestPayload() - d := new(DiscordPayload) - pl, err := d.Fork(p) + pl, err := dc.Fork(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "test/repo2 is forked to test/repo", pl.(*DiscordPayload).Embeds[0].Title) - assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "test/repo2 is forked to test/repo", pl.Embeds[0].Title) + assert.Empty(t, pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) t.Run("Push", func(t *testing.T) { p := pushTestPayload() - d := new(DiscordPayload) - pl, err := d.Push(p) + pl, err := dc.Push(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo:test] 2 new commits", pl.(*DiscordPayload).Embeds[0].Title) - assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo:test] 2 new commits", pl.Embeds[0].Title) + assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) t.Run("Issue", func(t *testing.T) { p := issueTestPayload() - d := new(DiscordPayload) p.Action = api.HookIssueOpened - pl, err := d.Issue(p) + pl, err := dc.Issue(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.(*DiscordPayload).Embeds[0].Title) - assert.Equal(t, "issue body", pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.Embeds[0].Title) + assert.Equal(t, "issue body", pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) p.Action = api.HookIssueClosed - pl, err = d.Issue(p) + pl, err = dc.Issue(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.(*DiscordPayload).Embeds[0].Title) - assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.Embeds[0].Title) + assert.Empty(t, pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) t.Run("IssueComment", func(t *testing.T) { p := issueCommentTestPayload() - d := new(DiscordPayload) - pl, err := d.IssueComment(p) + pl, err := dc.IssueComment(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.(*DiscordPayload).Embeds[0].Title) - assert.Equal(t, "more info needed", pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.Embeds[0].Title) + assert.Equal(t, "more info needed", pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) t.Run("PullRequest", func(t *testing.T) { p := pullRequestTestPayload() - d := new(DiscordPayload) - pl, err := d.PullRequest(p) + pl, err := dc.PullRequest(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.(*DiscordPayload).Embeds[0].Title) - assert.Equal(t, "fixes bug #2", pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.Embeds[0].Title) + assert.Equal(t, "fixes bug #2", pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) t.Run("PullRequestComment", func(t *testing.T) { p := pullRequestCommentTestPayload() - d := new(DiscordPayload) - pl, err := d.IssueComment(p) + pl, err := dc.IssueComment(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.(*DiscordPayload).Embeds[0].Title) - assert.Equal(t, "changes requested", pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.Embeds[0].Title) + assert.Equal(t, "changes requested", pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) t.Run("Review", func(t *testing.T) { p := pullRequestTestPayload() p.Action = api.HookIssueReviewed - d := new(DiscordPayload) - pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved) + pl, err := dc.Review(p, webhook_module.HookEventPullRequestReviewApproved) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.(*DiscordPayload).Embeds[0].Title) - assert.Equal(t, "good job", pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.Embeds[0].Title) + assert.Equal(t, "good job", pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) t.Run("Repository", func(t *testing.T) { p := repositoryTestPayload() - d := new(DiscordPayload) - pl, err := d.Repository(p) + pl, err := dc.Repository(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo] Repository created", pl.(*DiscordPayload).Embeds[0].Title) - assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo] Repository created", pl.Embeds[0].Title) + assert.Empty(t, pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) t.Run("Package", func(t *testing.T) { p := packageTestPayload() - d := new(DiscordPayload) - pl, err := d.Package(p) + pl, err := dc.Package(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "Package created: GiteaContainer:latest", pl.(*DiscordPayload).Embeds[0].Title) - assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "Package created: GiteaContainer:latest", pl.Embeds[0].Title) + assert.Empty(t, pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() - d := new(DiscordPayload) p.Action = api.HookWikiCreated - pl, err := d.Wiki(p) + pl, err := dc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.(*DiscordPayload).Embeds[0].Title) - assert.Equal(t, "Wiki change comment", pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.Embeds[0].Title) + assert.Equal(t, "Wiki change comment", pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) p.Action = api.HookWikiEdited - pl, err = d.Wiki(p) + pl, err = dc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.(*DiscordPayload).Embeds[0].Title) - assert.Equal(t, "Wiki change comment", pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.Embeds[0].Title) + assert.Equal(t, "Wiki change comment", pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) p.Action = api.HookWikiDeleted - pl, err = d.Wiki(p) + pl, err = dc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.(*DiscordPayload).Embeds[0].Title) - assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.Embeds[0].Title) + assert.Empty(t, pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) t.Run("Release", func(t *testing.T) { p := pullReleaseTestPayload() - d := new(DiscordPayload) - pl, err := d.Release(p) + pl, err := dc.Release(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - assert.Len(t, pl.(*DiscordPayload).Embeds, 1) - assert.Equal(t, "[test/repo] Release created: v1.0", pl.(*DiscordPayload).Embeds[0].Title) - assert.Equal(t, "Note of first stable release", pl.(*DiscordPayload).Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", pl.(*DiscordPayload).Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL) + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo] Release created: v1.0", pl.Embeds[0].Title) + assert.Equal(t, "Note of first stable release", pl.Embeds[0].Description) + assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", pl.Embeds[0].URL) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) } func TestDiscordJSONPayload(t *testing.T) { p := pushTestPayload() - - pl, err := new(DiscordPayload).Push(p) + data, err := p.JSONPayload() require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &DiscordPayload{}, pl) - json, err := pl.JSONPayload() + hook := &webhook_model.Webhook{ + RepoID: 3, + IsActive: true, + Type: webhook_module.DISCORD, + URL: "https://discord.example.com/", + Meta: `{}`, + HTTPMethod: "POST", + } + task := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadContent: string(data), + PayloadVersion: 2, + } + + req, reqBody, err := newDiscordRequest(context.Background(), hook, task) + require.NotNil(t, req) + require.NotNil(t, reqBody) require.NoError(t, err) - assert.NotEmpty(t, json) + + assert.Equal(t, "POST", req.Method) + assert.Equal(t, "https://discord.example.com/", req.URL.String()) + assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) + assert.Equal(t, "application/json", req.Header.Get("Content-Type")) + var body DiscordPayload + err = json.NewDecoder(req.Body).Decode(&body) + assert.NoError(t, err) + assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", body.Embeds[0].Description) } diff --git a/services/webhook/feishu.go b/services/webhook/feishu.go index 556443e70b..1ec436894b 100644 --- a/services/webhook/feishu.go +++ b/services/webhook/feishu.go @@ -4,11 +4,13 @@ package webhook import ( + "context" "fmt" + "net/http" "strings" + webhook_model "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" ) @@ -23,8 +25,8 @@ type ( } ) -func newFeishuTextPayload(text string) *FeishuPayload { - return &FeishuPayload{ +func newFeishuTextPayload(text string) FeishuPayload { + return FeishuPayload{ MsgType: "text", Content: struct { Text string `json:"text"` @@ -34,19 +36,8 @@ func newFeishuTextPayload(text string) *FeishuPayload { } } -// JSONPayload Marshals the FeishuPayload to json -func (f *FeishuPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(f, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -var _ PayloadConvertor = &FeishuPayload{} - // Create implements PayloadConvertor Create method -func (f *FeishuPayload) Create(p *api.CreatePayload) (api.Payloader, error) { +func (fc feishuConvertor) Create(p *api.CreatePayload) (FeishuPayload, error) { // created tag/branch refName := git.RefName(p.Ref).ShortName() text := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) @@ -55,7 +46,7 @@ func (f *FeishuPayload) Create(p *api.CreatePayload) (api.Payloader, error) { } // Delete implements PayloadConvertor Delete method -func (f *FeishuPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { +func (fc feishuConvertor) Delete(p *api.DeletePayload) (FeishuPayload, error) { // created tag/branch refName := git.RefName(p.Ref).ShortName() text := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) @@ -64,14 +55,14 @@ func (f *FeishuPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { } // Fork implements PayloadConvertor Fork method -func (f *FeishuPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { +func (fc feishuConvertor) Fork(p *api.ForkPayload) (FeishuPayload, error) { text := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) return newFeishuTextPayload(text), nil } // Push implements PayloadConvertor Push method -func (f *FeishuPayload) Push(p *api.PushPayload) (api.Payloader, error) { +func (fc feishuConvertor) Push(p *api.PushPayload) (FeishuPayload, error) { var ( branchName = git.RefName(p.Ref).ShortName() commitDesc string @@ -96,48 +87,40 @@ func (f *FeishuPayload) Push(p *api.PushPayload) (api.Payloader, error) { } // Issue implements PayloadConvertor Issue method -func (f *FeishuPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { +func (fc feishuConvertor) Issue(p *api.IssuePayload) (FeishuPayload, error) { title, link, by, operator, result, assignees := getIssuesInfo(p) - var res api.Payloader if assignees != "" { if p.Action == api.HookIssueAssigned || p.Action == api.HookIssueUnassigned || p.Action == api.HookIssueMilestoned { - res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, result, assignees, p.Issue.Body)) - } else { - res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, assignees, p.Issue.Body)) + return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, result, assignees, p.Issue.Body)), nil } - } else { - res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.Issue.Body)) + return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, assignees, p.Issue.Body)), nil } - return res, nil + return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.Issue.Body)), nil } // IssueComment implements PayloadConvertor IssueComment method -func (f *FeishuPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { +func (fc feishuConvertor) IssueComment(p *api.IssueCommentPayload) (FeishuPayload, error) { title, link, by, operator := getIssuesCommentInfo(p) return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.Comment.Body)), nil } // PullRequest implements PayloadConvertor PullRequest method -func (f *FeishuPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { +func (fc feishuConvertor) PullRequest(p *api.PullRequestPayload) (FeishuPayload, error) { title, link, by, operator, result, assignees := getPullRequestInfo(p) - var res api.Payloader if assignees != "" { if p.Action == api.HookIssueAssigned || p.Action == api.HookIssueUnassigned || p.Action == api.HookIssueMilestoned { - res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, result, assignees, p.PullRequest.Body)) - } else { - res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, assignees, p.PullRequest.Body)) + return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, result, assignees, p.PullRequest.Body)), nil } - } else { - res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.PullRequest.Body)) + return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, assignees, p.PullRequest.Body)), nil } - return res, nil + return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.PullRequest.Body)), nil } // Review implements PayloadConvertor Review method -func (f *FeishuPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) { +func (fc feishuConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (FeishuPayload, error) { action, err := parseHookPullRequestEventType(event) if err != nil { - return nil, err + return FeishuPayload{}, err } title := fmt.Sprintf("[%s] Pull request review %s : #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title) @@ -147,7 +130,7 @@ func (f *FeishuPayload) Review(p *api.PullRequestPayload, event webhook_module.H } // Repository implements PayloadConvertor Repository method -func (f *FeishuPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { +func (fc feishuConvertor) Repository(p *api.RepositoryPayload) (FeishuPayload, error) { var text string switch p.Action { case api.HookRepoCreated: @@ -158,30 +141,33 @@ func (f *FeishuPayload) Repository(p *api.RepositoryPayload) (api.Payloader, err return newFeishuTextPayload(text), nil } - return nil, nil + return FeishuPayload{}, nil } // Wiki implements PayloadConvertor Wiki method -func (f *FeishuPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) { +func (fc feishuConvertor) Wiki(p *api.WikiPayload) (FeishuPayload, error) { text, _, _ := getWikiPayloadInfo(p, noneLinkFormatter, true) return newFeishuTextPayload(text), nil } // Release implements PayloadConvertor Release method -func (f *FeishuPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { +func (fc feishuConvertor) Release(p *api.ReleasePayload) (FeishuPayload, error) { text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true) return newFeishuTextPayload(text), nil } -func (f *FeishuPayload) Package(p *api.PackagePayload) (api.Payloader, error) { +func (fc feishuConvertor) Package(p *api.PackagePayload) (FeishuPayload, error) { text, _ := getPackagePayloadInfo(p, noneLinkFormatter, true) return newFeishuTextPayload(text), nil } -// GetFeishuPayload converts a ding talk webhook into a FeishuPayload -func GetFeishuPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) { - return convertPayloader(new(FeishuPayload), p, event) +type feishuConvertor struct{} + +var _ payloadConvertor[FeishuPayload] = feishuConvertor{} + +func newFeishuRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { + return newJSONRequest(feishuConvertor{}, w, t, true) } diff --git a/services/webhook/feishu_test.go b/services/webhook/feishu_test.go index 98bc50dede..ef18333fd4 100644 --- a/services/webhook/feishu_test.go +++ b/services/webhook/feishu_test.go @@ -4,8 +4,11 @@ package webhook import ( + "context" "testing" + webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -14,199 +17,177 @@ import ( ) func TestFeishuPayload(t *testing.T) { + fc := feishuConvertor{} t.Run("Create", func(t *testing.T) { p := createTestPayload() - d := new(FeishuPayload) - pl, err := d.Create(p) + pl, err := fc.Create(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, `[test/repo] branch test created`, pl.(*FeishuPayload).Content.Text) + assert.Equal(t, `[test/repo] branch test created`, pl.Content.Text) }) t.Run("Delete", func(t *testing.T) { p := deleteTestPayload() - d := new(FeishuPayload) - pl, err := d.Delete(p) + pl, err := fc.Delete(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, `[test/repo] branch test deleted`, pl.(*FeishuPayload).Content.Text) + assert.Equal(t, `[test/repo] branch test deleted`, pl.Content.Text) }) t.Run("Fork", func(t *testing.T) { p := forkTestPayload() - d := new(FeishuPayload) - pl, err := d.Fork(p) + pl, err := fc.Fork(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, `test/repo2 is forked to test/repo`, pl.(*FeishuPayload).Content.Text) + assert.Equal(t, `test/repo2 is forked to test/repo`, pl.Content.Text) }) t.Run("Push", func(t *testing.T) { p := pushTestPayload() - d := new(FeishuPayload) - pl, err := d.Push(p) + pl, err := fc.Push(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, "[test/repo:test] \r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.(*FeishuPayload).Content.Text) + assert.Equal(t, "[test/repo:test] \r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.Content.Text) }) t.Run("Issue", func(t *testing.T) { p := issueTestPayload() - d := new(FeishuPayload) p.Action = api.HookIssueOpened - pl, err := d.Issue(p) + pl, err := fc.Issue(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, "[Issue-test/repo #2]: opened\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\nAssignees: user1\n\nissue body", pl.(*FeishuPayload).Content.Text) + assert.Equal(t, "[Issue-test/repo #2]: opened\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\nAssignees: user1\n\nissue body", pl.Content.Text) p.Action = api.HookIssueClosed - pl, err = d.Issue(p) + pl, err = fc.Issue(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, "[Issue-test/repo #2]: closed\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\nAssignees: user1\n\nissue body", pl.(*FeishuPayload).Content.Text) + assert.Equal(t, "[Issue-test/repo #2]: closed\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\nAssignees: user1\n\nissue body", pl.Content.Text) }) t.Run("IssueComment", func(t *testing.T) { p := issueCommentTestPayload() - d := new(FeishuPayload) - pl, err := d.IssueComment(p) + pl, err := fc.IssueComment(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, "[Comment-test/repo #2]: created\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\n\nmore info needed", pl.(*FeishuPayload).Content.Text) + assert.Equal(t, "[Comment-test/repo #2]: created\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\n\nmore info needed", pl.Content.Text) }) t.Run("PullRequest", func(t *testing.T) { p := pullRequestTestPayload() - d := new(FeishuPayload) - pl, err := d.PullRequest(p) + pl, err := fc.PullRequest(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, "[PullRequest-test/repo #12]: opened\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\nAssignees: user1\n\nfixes bug #2", pl.(*FeishuPayload).Content.Text) + assert.Equal(t, "[PullRequest-test/repo #12]: opened\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\nAssignees: user1\n\nfixes bug #2", pl.Content.Text) }) t.Run("PullRequestComment", func(t *testing.T) { p := pullRequestCommentTestPayload() - d := new(FeishuPayload) - pl, err := d.IssueComment(p) + pl, err := fc.IssueComment(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, "[Comment-test/repo #12]: created\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\n\nchanges requested", pl.(*FeishuPayload).Content.Text) + assert.Equal(t, "[Comment-test/repo #12]: created\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\n\nchanges requested", pl.Content.Text) }) t.Run("Review", func(t *testing.T) { p := pullRequestTestPayload() p.Action = api.HookIssueReviewed - d := new(FeishuPayload) - pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved) + pl, err := fc.Review(p, webhook_module.HookEventPullRequestReviewApproved) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug\r\n\r\ngood job", pl.(*FeishuPayload).Content.Text) + assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug\r\n\r\ngood job", pl.Content.Text) }) t.Run("Repository", func(t *testing.T) { p := repositoryTestPayload() - d := new(FeishuPayload) - pl, err := d.Repository(p) + pl, err := fc.Repository(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, "[test/repo] Repository created", pl.(*FeishuPayload).Content.Text) + assert.Equal(t, "[test/repo] Repository created", pl.Content.Text) }) t.Run("Package", func(t *testing.T) { p := packageTestPayload() - d := new(FeishuPayload) - pl, err := d.Package(p) + pl, err := fc.Package(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.(*FeishuPayload).Content.Text) + assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.Content.Text) }) t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() - d := new(FeishuPayload) p.Action = api.HookWikiCreated - pl, err := d.Wiki(p) + pl, err := fc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.(*FeishuPayload).Content.Text) + assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.Content.Text) p.Action = api.HookWikiEdited - pl, err = d.Wiki(p) + pl, err = fc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.(*FeishuPayload).Content.Text) + assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.Content.Text) p.Action = api.HookWikiDeleted - pl, err = d.Wiki(p) + pl, err = fc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.(*FeishuPayload).Content.Text) + assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.Content.Text) }) t.Run("Release", func(t *testing.T) { p := pullReleaseTestPayload() - d := new(FeishuPayload) - pl, err := d.Release(p) + pl, err := fc.Release(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.(*FeishuPayload).Content.Text) + assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.Content.Text) }) } func TestFeishuJSONPayload(t *testing.T) { p := pushTestPayload() - - pl, err := new(FeishuPayload).Push(p) + data, err := p.JSONPayload() require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &FeishuPayload{}, pl) - json, err := pl.JSONPayload() + hook := &webhook_model.Webhook{ + RepoID: 3, + IsActive: true, + Type: webhook_module.FEISHU, + URL: "https://feishu.example.com/", + Meta: `{}`, + HTTPMethod: "POST", + } + task := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadContent: string(data), + PayloadVersion: 2, + } + + req, reqBody, err := newFeishuRequest(context.Background(), hook, task) + require.NotNil(t, req) + require.NotNil(t, reqBody) require.NoError(t, err) - assert.NotEmpty(t, json) + + assert.Equal(t, "POST", req.Method) + assert.Equal(t, "https://feishu.example.com/", req.URL.String()) + assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) + assert.Equal(t, "application/json", req.Header.Get("Content-Type")) + var body FeishuPayload + err = json.NewDecoder(req.Body).Decode(&body) + assert.NoError(t, err) + assert.Equal(t, "[test/repo:test] \r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", body.Content.Text) } diff --git a/services/webhook/matrix.go b/services/webhook/matrix.go index 602d16ef39..0329804a8b 100644 --- a/services/webhook/matrix.go +++ b/services/webhook/matrix.go @@ -4,11 +4,12 @@ package webhook import ( + "bytes" + "context" "crypto/sha1" "encoding/hex" - "errors" "fmt" - "html" + "net/http" "net/url" "regexp" "strings" @@ -23,6 +24,37 @@ import ( webhook_module "code.gitea.io/gitea/modules/webhook" ) +func newMatrixRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { + meta := &MatrixMeta{} + if err := json.Unmarshal([]byte(w.Meta), meta); err != nil { + return nil, nil, fmt.Errorf("GetMatrixPayload meta json: %w", err) + } + mc := matrixConvertor{ + MsgType: messageTypeText[meta.MessageType], + } + payload, err := newPayload(mc, []byte(t.PayloadContent), t.EventType) + if err != nil { + return nil, nil, err + } + + body, err := json.MarshalIndent(payload, "", " ") + if err != nil { + return nil, nil, err + } + + txnID, err := getMatrixTxnID(body) + if err != nil { + return nil, nil, err + } + req, err := http.NewRequest(http.MethodPut, w.URL+"/"+txnID, bytes.NewReader(body)) + if err != nil { + return nil, nil, err + } + req.Header.Set("Content-Type", "application/json") + + return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body) // likely useless, but has always been sent historially +} + const matrixPayloadSizeLimit = 1024 * 64 // MatrixMeta contains the Matrix metadata @@ -46,8 +78,6 @@ func GetMatrixHook(w *webhook_model.Webhook) *MatrixMeta { return s } -var _ PayloadConvertor = &MatrixPayload{} - // MatrixPayload contains payload for a Matrix room type MatrixPayload struct { Body string `json:"body"` @@ -57,90 +87,79 @@ type MatrixPayload struct { Commits []*api.PayloadCommit `json:"io.gitea.commits,omitempty"` } -// JSONPayload Marshals the MatrixPayload to json -func (m *MatrixPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(m, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil +var _ payloadConvertor[MatrixPayload] = matrixConvertor{} + +type matrixConvertor struct { + MsgType string } -// MatrixLinkFormatter creates a link compatible with Matrix -func MatrixLinkFormatter(url, text string) string { - return fmt.Sprintf(`<a href="%s">%s</a>`, html.EscapeString(url), html.EscapeString(text)) +func (m matrixConvertor) newPayload(text string, commits ...*api.PayloadCommit) (MatrixPayload, error) { + return MatrixPayload{ + Body: getMessageBody(text), + MsgType: m.MsgType, + Format: "org.matrix.custom.html", + FormattedBody: text, + Commits: commits, + }, nil } -// MatrixLinkToRef Matrix-formatter link to a repo ref -func MatrixLinkToRef(repoURL, ref string) string { - refName := git.RefName(ref).ShortName() - switch { - case strings.HasPrefix(ref, git.BranchPrefix): - return MatrixLinkFormatter(repoURL+"/src/branch/"+util.PathEscapeSegments(refName), refName) - case strings.HasPrefix(ref, git.TagPrefix): - return MatrixLinkFormatter(repoURL+"/src/tag/"+util.PathEscapeSegments(refName), refName) - default: - return MatrixLinkFormatter(repoURL+"/src/commit/"+util.PathEscapeSegments(refName), refName) - } -} - -// Create implements PayloadConvertor Create method -func (m *MatrixPayload) Create(p *api.CreatePayload) (api.Payloader, error) { - repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) +// Create implements payloadConvertor Create method +func (m matrixConvertor) Create(p *api.CreatePayload) (MatrixPayload, error) { + repoLink := htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) refLink := MatrixLinkToRef(p.Repo.HTMLURL, p.Ref) text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName) - return getMatrixPayload(text, nil, m.MsgType), nil + return m.newPayload(text) } // Delete composes Matrix payload for delete a branch or tag. -func (m *MatrixPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { +func (m matrixConvertor) Delete(p *api.DeletePayload) (MatrixPayload, error) { refName := git.RefName(p.Ref).ShortName() - repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) + repoLink := htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName) - return getMatrixPayload(text, nil, m.MsgType), nil + return m.newPayload(text) } // Fork composes Matrix payload for forked by a repository. -func (m *MatrixPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { - baseLink := MatrixLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) - forkLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) +func (m matrixConvertor) Fork(p *api.ForkPayload) (MatrixPayload, error) { + baseLink := htmlLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) + forkLink := htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink) - return getMatrixPayload(text, nil, m.MsgType), nil + return m.newPayload(text) } -// Issue implements PayloadConvertor Issue method -func (m *MatrixPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { - text, _, _, _ := getIssuesPayloadInfo(p, MatrixLinkFormatter, true) +// Issue implements payloadConvertor Issue method +func (m matrixConvertor) Issue(p *api.IssuePayload) (MatrixPayload, error) { + text, _, _, _ := getIssuesPayloadInfo(p, htmlLinkFormatter, true) - return getMatrixPayload(text, nil, m.MsgType), nil + return m.newPayload(text) } -// IssueComment implements PayloadConvertor IssueComment method -func (m *MatrixPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { - text, _, _ := getIssueCommentPayloadInfo(p, MatrixLinkFormatter, true) +// IssueComment implements payloadConvertor IssueComment method +func (m matrixConvertor) IssueComment(p *api.IssueCommentPayload) (MatrixPayload, error) { + text, _, _ := getIssueCommentPayloadInfo(p, htmlLinkFormatter, true) - return getMatrixPayload(text, nil, m.MsgType), nil + return m.newPayload(text) } -// Wiki implements PayloadConvertor Wiki method -func (m *MatrixPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) { - text, _, _ := getWikiPayloadInfo(p, MatrixLinkFormatter, true) +// Wiki implements payloadConvertor Wiki method +func (m matrixConvertor) Wiki(p *api.WikiPayload) (MatrixPayload, error) { + text, _, _ := getWikiPayloadInfo(p, htmlLinkFormatter, true) - return getMatrixPayload(text, nil, m.MsgType), nil + return m.newPayload(text) } -// Release implements PayloadConvertor Release method -func (m *MatrixPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { - text, _ := getReleasePayloadInfo(p, MatrixLinkFormatter, true) +// Release implements payloadConvertor Release method +func (m matrixConvertor) Release(p *api.ReleasePayload) (MatrixPayload, error) { + text, _ := getReleasePayloadInfo(p, htmlLinkFormatter, true) - return getMatrixPayload(text, nil, m.MsgType), nil + return m.newPayload(text) } -// Push implements PayloadConvertor Push method -func (m *MatrixPayload) Push(p *api.PushPayload) (api.Payloader, error) { +// Push implements payloadConvertor Push method +func (m matrixConvertor) Push(p *api.PushPayload) (MatrixPayload, error) { var commitDesc string if p.TotalCommits == 1 { @@ -149,13 +168,13 @@ func (m *MatrixPayload) Push(p *api.PushPayload) (api.Payloader, error) { commitDesc = fmt.Sprintf("%d commits", p.TotalCommits) } - repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) + repoLink := htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) branchLink := MatrixLinkToRef(p.Repo.HTMLURL, p.Ref) text := fmt.Sprintf("[%s] %s pushed %s to %s:<br>", repoLink, p.Pusher.UserName, commitDesc, branchLink) // for each commit, generate a new line text for i, commit := range p.Commits { - text += fmt.Sprintf("%s: %s - %s", MatrixLinkFormatter(commit.URL, commit.ID[:7]), commit.Message, commit.Author.Name) + text += fmt.Sprintf("%s: %s - %s", htmlLinkFormatter(commit.URL, commit.ID[:7]), commit.Message, commit.Author.Name) // add linebreak to each commit but the last if i < len(p.Commits)-1 { text += "<br>" @@ -163,41 +182,41 @@ func (m *MatrixPayload) Push(p *api.PushPayload) (api.Payloader, error) { } - return getMatrixPayload(text, p.Commits, m.MsgType), nil + return m.newPayload(text, p.Commits...) } -// PullRequest implements PayloadConvertor PullRequest method -func (m *MatrixPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { - text, _, _, _ := getPullRequestPayloadInfo(p, MatrixLinkFormatter, true) +// PullRequest implements payloadConvertor PullRequest method +func (m matrixConvertor) PullRequest(p *api.PullRequestPayload) (MatrixPayload, error) { + text, _, _, _ := getPullRequestPayloadInfo(p, htmlLinkFormatter, true) - return getMatrixPayload(text, nil, m.MsgType), nil + return m.newPayload(text) } -// Review implements PayloadConvertor Review method -func (m *MatrixPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) { - senderLink := MatrixLinkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) +// Review implements payloadConvertor Review method +func (m matrixConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (MatrixPayload, error) { + senderLink := htmlLinkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) - titleLink := MatrixLinkFormatter(p.PullRequest.HTMLURL, title) - repoLink := MatrixLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) + titleLink := htmlLinkFormatter(p.PullRequest.HTMLURL, title) + repoLink := htmlLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) var text string switch p.Action { case api.HookIssueReviewed: action, err := parseHookPullRequestEventType(event) if err != nil { - return nil, err + return MatrixPayload{}, err } text = fmt.Sprintf("[%s] Pull request review %s: %s by %s", repoLink, action, titleLink, senderLink) } - return getMatrixPayload(text, nil, m.MsgType), nil + return m.newPayload(text) } -// Repository implements PayloadConvertor Repository method -func (m *MatrixPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { - senderLink := MatrixLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - repoLink := MatrixLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) +// Repository implements payloadConvertor Repository method +func (m matrixConvertor) Repository(p *api.RepositoryPayload) (MatrixPayload, error) { + senderLink := htmlLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) + repoLink := htmlLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) var text string switch p.Action { @@ -206,13 +225,12 @@ func (m *MatrixPayload) Repository(p *api.RepositoryPayload) (api.Payloader, err case api.HookRepoDeleted: text = fmt.Sprintf("[%s] Repository deleted by %s", repoLink, senderLink) } - - return getMatrixPayload(text, nil, m.MsgType), nil + return m.newPayload(text) } -func (m *MatrixPayload) Package(p *api.PackagePayload) (api.Payloader, error) { - senderLink := MatrixLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - packageLink := MatrixLinkFormatter(p.Package.HTMLURL, p.Package.Name) +func (m matrixConvertor) Package(p *api.PackagePayload) (MatrixPayload, error) { + senderLink := htmlLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) + packageLink := htmlLinkFormatter(p.Package.HTMLURL, p.Package.Name) var text string switch p.Action { @@ -222,31 +240,7 @@ func (m *MatrixPayload) Package(p *api.PackagePayload) (api.Payloader, error) { text = fmt.Sprintf("[%s] Package deleted by %s", packageLink, senderLink) } - return getMatrixPayload(text, nil, m.MsgType), nil -} - -// GetMatrixPayload converts a Matrix webhook into a MatrixPayload -func GetMatrixPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) { - s := new(MatrixPayload) - - matrix := &MatrixMeta{} - if err := json.Unmarshal([]byte(meta), &matrix); err != nil { - return s, errors.New("GetMatrixPayload meta json:" + err.Error()) - } - - s.MsgType = messageTypeText[matrix.MessageType] - - return convertPayloader(s, p, event) -} - -func getMatrixPayload(text string, commits []*api.PayloadCommit, msgType string) *MatrixPayload { - p := MatrixPayload{} - p.FormattedBody = text - p.Body = getMessageBody(text) - p.Format = "org.matrix.custom.html" - p.MsgType = msgType - p.Commits = commits - return &p + return m.newPayload(text) } var urlRegex = regexp.MustCompile(`<a [^>]*?href="([^">]*?)">(.*?)</a>`) @@ -271,3 +265,16 @@ func getMatrixTxnID(payload []byte) (string, error) { return hex.EncodeToString(h.Sum(nil)), nil } + +// MatrixLinkToRef Matrix-formatter link to a repo ref +func MatrixLinkToRef(repoURL, ref string) string { + refName := git.RefName(ref).ShortName() + switch { + case strings.HasPrefix(ref, git.BranchPrefix): + return htmlLinkFormatter(repoURL+"/src/branch/"+util.PathEscapeSegments(refName), refName) + case strings.HasPrefix(ref, git.TagPrefix): + return htmlLinkFormatter(repoURL+"/src/tag/"+util.PathEscapeSegments(refName), refName) + default: + return htmlLinkFormatter(repoURL+"/src/commit/"+util.PathEscapeSegments(refName), refName) + } +} diff --git a/services/webhook/matrix_test.go b/services/webhook/matrix_test.go index 99a22fbd7e..058f8e3c5f 100644 --- a/services/webhook/matrix_test.go +++ b/services/webhook/matrix_test.go @@ -4,8 +4,11 @@ package webhook import ( + "context" "testing" + webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -14,217 +17,213 @@ import ( ) func TestMatrixPayload(t *testing.T) { + mc := matrixConvertor{ + MsgType: "m.text", + } + t.Run("Create", func(t *testing.T) { p := createTestPayload() - d := new(MatrixPayload) - pl, err := d.Create(p) + pl, err := mc.Create(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo):[test](http://localhost:3000/test/repo/src/branch/test)] branch created by user1", pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>:<a href="http://localhost:3000/test/repo/src/branch/test">test</a>] branch created by user1`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo):[test](http://localhost:3000/test/repo/src/branch/test)] branch created by user1", pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>:<a href="http://localhost:3000/test/repo/src/branch/test">test</a>] branch created by user1`, pl.FormattedBody) }) t.Run("Delete", func(t *testing.T) { p := deleteTestPayload() - d := new(MatrixPayload) - pl, err := d.Delete(p) + pl, err := mc.Delete(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo):test] branch deleted by user1", pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>:test] branch deleted by user1`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo):test] branch deleted by user1", pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>:test] branch deleted by user1`, pl.FormattedBody) }) t.Run("Fork", func(t *testing.T) { p := forkTestPayload() - d := new(MatrixPayload) - pl, err := d.Fork(p) + pl, err := mc.Fork(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[test/repo2](http://localhost:3000/test/repo2) is forked to [test/repo](http://localhost:3000/test/repo)", pl.(*MatrixPayload).Body) - assert.Equal(t, `<a href="http://localhost:3000/test/repo2">test/repo2</a> is forked to <a href="http://localhost:3000/test/repo">test/repo</a>`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[test/repo2](http://localhost:3000/test/repo2) is forked to [test/repo](http://localhost:3000/test/repo)", pl.Body) + assert.Equal(t, `<a href="http://localhost:3000/test/repo2">test/repo2</a> is forked to <a href="http://localhost:3000/test/repo">test/repo</a>`, pl.FormattedBody) }) t.Run("Push", func(t *testing.T) { p := pushTestPayload() - d := new(MatrixPayload) - pl, err := d.Push(p) + pl, err := mc.Push(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] user1 pushed 2 commits to [test](http://localhost:3000/test/repo/src/branch/test):\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1", pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] user1 pushed 2 commits to <a href="http://localhost:3000/test/repo/src/branch/test">test</a>:<br><a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778">2020558</a>: commit message - user1<br><a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778">2020558</a>: commit message - user1`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] user1 pushed 2 commits to [test](http://localhost:3000/test/repo/src/branch/test):\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1", pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] user1 pushed 2 commits to <a href="http://localhost:3000/test/repo/src/branch/test">test</a>:<br><a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778">2020558</a>: commit message - user1<br><a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778">2020558</a>: commit message - user1`, pl.FormattedBody) }) t.Run("Issue", func(t *testing.T) { p := issueTestPayload() - d := new(MatrixPayload) p.Action = api.HookIssueOpened - pl, err := d.Issue(p) + pl, err := mc.Issue(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue opened: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Issue opened: <a href="http://localhost:3000/test/repo/issues/2">#2 crash</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue opened: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Issue opened: <a href="http://localhost:3000/test/repo/issues/2">#2 crash</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.FormattedBody) p.Action = api.HookIssueClosed - pl, err = d.Issue(p) + pl, err = mc.Issue(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue closed: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Issue closed: <a href="http://localhost:3000/test/repo/issues/2">#2 crash</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue closed: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Issue closed: <a href="http://localhost:3000/test/repo/issues/2">#2 crash</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.FormattedBody) }) t.Run("IssueComment", func(t *testing.T) { p := issueCommentTestPayload() - d := new(MatrixPayload) - pl, err := d.IssueComment(p) + pl, err := mc.IssueComment(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on issue [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] New comment on issue <a href="http://localhost:3000/test/repo/issues/2">#2 crash</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on issue [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] New comment on issue <a href="http://localhost:3000/test/repo/issues/2">#2 crash</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.FormattedBody) }) t.Run("PullRequest", func(t *testing.T) { p := pullRequestTestPayload() - d := new(MatrixPayload) - pl, err := d.PullRequest(p) + pl, err := mc.PullRequest(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request opened: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Pull request opened: <a href="http://localhost:3000/test/repo/pulls/12">#12 Fix bug</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request opened: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Pull request opened: <a href="http://localhost:3000/test/repo/pulls/12">#12 Fix bug</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.FormattedBody) }) t.Run("PullRequestComment", func(t *testing.T) { p := pullRequestCommentTestPayload() - d := new(MatrixPayload) - pl, err := d.IssueComment(p) + pl, err := mc.IssueComment(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on pull request [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] New comment on pull request <a href="http://localhost:3000/test/repo/pulls/12">#12 Fix bug</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on pull request [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] New comment on pull request <a href="http://localhost:3000/test/repo/pulls/12">#12 Fix bug</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.FormattedBody) }) t.Run("Review", func(t *testing.T) { p := pullRequestTestPayload() p.Action = api.HookIssueReviewed - d := new(MatrixPayload) - pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved) + pl, err := mc.Review(p, webhook_module.HookEventPullRequestReviewApproved) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request review approved: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Pull request review approved: <a href="http://localhost:3000/test/repo/pulls/12">#12 Fix bug</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request review approved: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Pull request review approved: <a href="http://localhost:3000/test/repo/pulls/12">#12 Fix bug</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.FormattedBody) }) t.Run("Repository", func(t *testing.T) { p := repositoryTestPayload() - d := new(MatrixPayload) - pl, err := d.Repository(p) + pl, err := mc.Repository(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, `[[test/repo](http://localhost:3000/test/repo)] Repository created by [user1](https://try.gitea.io/user1)`, pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Repository created by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, `[[test/repo](http://localhost:3000/test/repo)] Repository created by [user1](https://try.gitea.io/user1)`, pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Repository created by <a href="https://try.gitea.io/user1">user1</a>`, pl.FormattedBody) }) t.Run("Package", func(t *testing.T) { p := packageTestPayload() - d := new(MatrixPayload) - pl, err := d.Package(p) + pl, err := mc.Package(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, `[[GiteaContainer](http://localhost:3000/user1/-/packages/container/GiteaContainer/latest)] Package published by [user1](https://try.gitea.io/user1)`, pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/user1/-/packages/container/GiteaContainer/latest">GiteaContainer</a>] Package published by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, `[[GiteaContainer](http://localhost:3000/user1/-/packages/container/GiteaContainer/latest)] Package published by [user1](https://try.gitea.io/user1)`, pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/user1/-/packages/container/GiteaContainer/latest">GiteaContainer</a>] Package published by <a href="https://try.gitea.io/user1">user1</a>`, pl.FormattedBody) }) t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() - d := new(MatrixPayload) p.Action = api.HookWikiCreated - pl, err := d.Wiki(p) + pl, err := mc.Wiki(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New wiki page '[index](http://localhost:3000/test/repo/wiki/index)' (Wiki change comment) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] New wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' (Wiki change comment) by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New wiki page '[index](http://localhost:3000/test/repo/wiki/index)' (Wiki change comment) by [user1](https://try.gitea.io/user1)", pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] New wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' (Wiki change comment) by <a href="https://try.gitea.io/user1">user1</a>`, pl.FormattedBody) p.Action = api.HookWikiEdited - pl, err = d.Wiki(p) + pl, err = mc.Wiki(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Wiki page '[index](http://localhost:3000/test/repo/wiki/index)' edited (Wiki change comment) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' edited (Wiki change comment) by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Wiki page '[index](http://localhost:3000/test/repo/wiki/index)' edited (Wiki change comment) by [user1](https://try.gitea.io/user1)", pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' edited (Wiki change comment) by <a href="https://try.gitea.io/user1">user1</a>`, pl.FormattedBody) p.Action = api.HookWikiDeleted - pl, err = d.Wiki(p) + pl, err = mc.Wiki(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Wiki page '[index](http://localhost:3000/test/repo/wiki/index)' deleted by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' deleted by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Wiki page '[index](http://localhost:3000/test/repo/wiki/index)' deleted by [user1](https://try.gitea.io/user1)", pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' deleted by <a href="https://try.gitea.io/user1">user1</a>`, pl.FormattedBody) }) t.Run("Release", func(t *testing.T) { p := pullReleaseTestPayload() - d := new(MatrixPayload) - pl, err := d.Release(p) + pl, err := mc.Release(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Release created: [v1.0](http://localhost:3000/test/repo/releases/tag/v1.0) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Release created: <a href="http://localhost:3000/test/repo/releases/tag/v1.0">v1.0</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Release created: [v1.0](http://localhost:3000/test/repo/releases/tag/v1.0) by [user1](https://try.gitea.io/user1)", pl.Body) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Release created: <a href="http://localhost:3000/test/repo/releases/tag/v1.0">v1.0</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.FormattedBody) }) } func TestMatrixJSONPayload(t *testing.T) { p := pushTestPayload() - - pl, err := new(MatrixPayload).Push(p) + data, err := p.JSONPayload() require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MatrixPayload{}, pl) - json, err := pl.JSONPayload() + hook := &webhook_model.Webhook{ + RepoID: 3, + IsActive: true, + Type: webhook_module.MATRIX, + URL: "https://matrix.example.com/_matrix/client/r0/rooms/ROOM_ID/send/m.room.message", + Meta: `{"message_type":0}`, // text + } + task := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadContent: string(data), + PayloadVersion: 2, + } + + req, reqBody, err := newMatrixRequest(context.Background(), hook, task) + require.NotNil(t, req) + require.NotNil(t, reqBody) require.NoError(t, err) - assert.NotEmpty(t, json) + + assert.Equal(t, "PUT", req.Method) + assert.Equal(t, "/_matrix/client/r0/rooms/ROOM_ID/send/m.room.message/6db5dc1e282529a8c162c7fe93dd2667494eeb51", req.URL.Path) + assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) + assert.Equal(t, "application/json", req.Header.Get("Content-Type")) + var body MatrixPayload + err = json.NewDecoder(req.Body).Decode(&body) + assert.NoError(t, err) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] user1 pushed 2 commits to [test](http://localhost:3000/test/repo/src/branch/test):\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1", body.Body) } func Test_getTxnID(t *testing.T) { diff --git a/services/webhook/msteams.go b/services/webhook/msteams.go index 37810b4cd3..99d0106184 100644 --- a/services/webhook/msteams.go +++ b/services/webhook/msteams.go @@ -4,12 +4,14 @@ package webhook import ( + "context" "fmt" + "net/http" "net/url" "strings" + webhook_model "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -56,19 +58,8 @@ type ( } ) -// JSONPayload Marshals the MSTeamsPayload to json -func (m *MSTeamsPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(m, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -var _ PayloadConvertor = &MSTeamsPayload{} - // Create implements PayloadConvertor Create method -func (m *MSTeamsPayload) Create(p *api.CreatePayload) (api.Payloader, error) { +func (m msteamsConvertor) Create(p *api.CreatePayload) (MSTeamsPayload, error) { // created tag/branch refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) @@ -85,7 +76,7 @@ func (m *MSTeamsPayload) Create(p *api.CreatePayload) (api.Payloader, error) { } // Delete implements PayloadConvertor Delete method -func (m *MSTeamsPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { +func (m msteamsConvertor) Delete(p *api.DeletePayload) (MSTeamsPayload, error) { // deleted tag/branch refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) @@ -102,7 +93,7 @@ func (m *MSTeamsPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { } // Fork implements PayloadConvertor Fork method -func (m *MSTeamsPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { +func (m msteamsConvertor) Fork(p *api.ForkPayload) (MSTeamsPayload, error) { title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) return createMSTeamsPayload( @@ -117,7 +108,7 @@ func (m *MSTeamsPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { } // Push implements PayloadConvertor Push method -func (m *MSTeamsPayload) Push(p *api.PushPayload) (api.Payloader, error) { +func (m msteamsConvertor) Push(p *api.PushPayload) (MSTeamsPayload, error) { var ( branchName = git.RefName(p.Ref).ShortName() commitDesc string @@ -160,7 +151,7 @@ func (m *MSTeamsPayload) Push(p *api.PushPayload) (api.Payloader, error) { } // Issue implements PayloadConvertor Issue method -func (m *MSTeamsPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { +func (m msteamsConvertor) Issue(p *api.IssuePayload) (MSTeamsPayload, error) { title, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, false) return createMSTeamsPayload( @@ -175,7 +166,7 @@ func (m *MSTeamsPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { } // IssueComment implements PayloadConvertor IssueComment method -func (m *MSTeamsPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { +func (m msteamsConvertor) IssueComment(p *api.IssueCommentPayload) (MSTeamsPayload, error) { title, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false) return createMSTeamsPayload( @@ -190,7 +181,7 @@ func (m *MSTeamsPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader } // PullRequest implements PayloadConvertor PullRequest method -func (m *MSTeamsPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { +func (m msteamsConvertor) PullRequest(p *api.PullRequestPayload) (MSTeamsPayload, error) { title, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false) return createMSTeamsPayload( @@ -205,14 +196,14 @@ func (m *MSTeamsPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, } // Review implements PayloadConvertor Review method -func (m *MSTeamsPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) { +func (m msteamsConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (MSTeamsPayload, error) { var text, title string var color int switch p.Action { case api.HookIssueReviewed: action, err := parseHookPullRequestEventType(event) if err != nil { - return nil, err + return MSTeamsPayload{}, err } title = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title) @@ -242,7 +233,7 @@ func (m *MSTeamsPayload) Review(p *api.PullRequestPayload, event webhook_module. } // Repository implements PayloadConvertor Repository method -func (m *MSTeamsPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { +func (m msteamsConvertor) Repository(p *api.RepositoryPayload) (MSTeamsPayload, error) { var title, url string var color int switch p.Action { @@ -267,7 +258,7 @@ func (m *MSTeamsPayload) Repository(p *api.RepositoryPayload) (api.Payloader, er } // Wiki implements PayloadConvertor Wiki method -func (m *MSTeamsPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) { +func (m msteamsConvertor) Wiki(p *api.WikiPayload) (MSTeamsPayload, error) { title, color, _ := getWikiPayloadInfo(p, noneLinkFormatter, false) return createMSTeamsPayload( @@ -282,7 +273,7 @@ func (m *MSTeamsPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) { } // Release implements PayloadConvertor Release method -func (m *MSTeamsPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { +func (m msteamsConvertor) Release(p *api.ReleasePayload) (MSTeamsPayload, error) { title, color := getReleasePayloadInfo(p, noneLinkFormatter, false) return createMSTeamsPayload( @@ -296,7 +287,7 @@ func (m *MSTeamsPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { ), nil } -func (m *MSTeamsPayload) Package(p *api.PackagePayload) (api.Payloader, error) { +func (m msteamsConvertor) Package(p *api.PackagePayload) (MSTeamsPayload, error) { title, color := getPackagePayloadInfo(p, noneLinkFormatter, false) return createMSTeamsPayload( @@ -310,12 +301,7 @@ func (m *MSTeamsPayload) Package(p *api.PackagePayload) (api.Payloader, error) { ), nil } -// GetMSTeamsPayload converts a MSTeams webhook into a MSTeamsPayload -func GetMSTeamsPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) { - return convertPayloader(new(MSTeamsPayload), p, event) -} - -func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTarget string, color int, fact *MSTeamsFact) *MSTeamsPayload { +func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTarget string, color int, fact *MSTeamsFact) MSTeamsPayload { facts := make([]MSTeamsFact, 0, 2) if r != nil { facts = append(facts, MSTeamsFact{ @@ -327,7 +313,7 @@ func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTar facts = append(facts, *fact) } - return &MSTeamsPayload{ + return MSTeamsPayload{ Type: "MessageCard", Context: "https://schema.org/extensions", ThemeColor: fmt.Sprintf("%x", color), @@ -356,3 +342,11 @@ func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTar }, } } + +type msteamsConvertor struct{} + +var _ payloadConvertor[MSTeamsPayload] = msteamsConvertor{} + +func newMSTeamsRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { + return newJSONRequest(msteamsConvertor{}, w, t, true) +} diff --git a/services/webhook/msteams_test.go b/services/webhook/msteams_test.go index 8d1aed6040..01e08b918e 100644 --- a/services/webhook/msteams_test.go +++ b/services/webhook/msteams_test.go @@ -4,8 +4,11 @@ package webhook import ( + "context" "testing" + webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -14,22 +17,20 @@ import ( ) func TestMSTeamsPayload(t *testing.T) { + mc := msteamsConvertor{} t.Run("Create", func(t *testing.T) { p := createTestPayload() - d := new(MSTeamsPayload) - pl, err := d.Create(p) + pl, err := mc.Create(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo] branch test created", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo] branch test created", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo] branch test created", pl.Title) + assert.Equal(t, "[test/repo] branch test created", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Empty(t, pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repo.FullName, fact.Value) } else if fact.Name == "branch:" { @@ -38,27 +39,24 @@ func TestMSTeamsPayload(t *testing.T) { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.PotentialAction[0].Targets[0].URI) }) t.Run("Delete", func(t *testing.T) { p := deleteTestPayload() - d := new(MSTeamsPayload) - pl, err := d.Delete(p) + pl, err := mc.Delete(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo] branch test deleted", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo] branch test deleted", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo] branch test deleted", pl.Title) + assert.Equal(t, "[test/repo] branch test deleted", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Empty(t, pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repo.FullName, fact.Value) } else if fact.Name == "branch:" { @@ -67,27 +65,24 @@ func TestMSTeamsPayload(t *testing.T) { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.PotentialAction[0].Targets[0].URI) }) t.Run("Fork", func(t *testing.T) { p := forkTestPayload() - d := new(MSTeamsPayload) - pl, err := d.Fork(p) + pl, err := mc.Fork(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "test/repo2 is forked to test/repo", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "test/repo2 is forked to test/repo", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "test/repo2 is forked to test/repo", pl.Title) + assert.Equal(t, "test/repo2 is forked to test/repo", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Empty(t, pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repo.FullName, fact.Value) } else if fact.Name == "Forkee:" { @@ -96,27 +91,24 @@ func TestMSTeamsPayload(t *testing.T) { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo", pl.PotentialAction[0].Targets[0].URI) }) t.Run("Push", func(t *testing.T) { p := pushTestPayload() - d := new(MSTeamsPayload) - pl, err := d.Push(p) + pl, err := mc.Push(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo:test] 2 new commits", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo:test] 2 new commits", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo:test] 2 new commits", pl.Title) + assert.Equal(t, "[test/repo:test] 2 new commits", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repo.FullName, fact.Value) } else if fact.Name == "Commit count:" { @@ -125,28 +117,25 @@ func TestMSTeamsPayload(t *testing.T) { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.PotentialAction[0].Targets[0].URI) }) t.Run("Issue", func(t *testing.T) { p := issueTestPayload() - d := new(MSTeamsPayload) p.Action = api.HookIssueOpened - pl, err := d.Issue(p) + pl, err := mc.Issue(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Equal(t, "issue body", pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.Title) + assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Equal(t, "issue body", pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repository.FullName, fact.Value) } else if fact.Name == "Issue #:" { @@ -155,23 +144,21 @@ func TestMSTeamsPayload(t *testing.T) { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.PotentialAction[0].Targets[0].URI) p.Action = api.HookIssueClosed - pl, err = d.Issue(p) + pl, err = mc.Issue(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.Title) + assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Empty(t, pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repository.FullName, fact.Value) } else if fact.Name == "Issue #:" { @@ -180,27 +167,24 @@ func TestMSTeamsPayload(t *testing.T) { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.PotentialAction[0].Targets[0].URI) }) t.Run("IssueComment", func(t *testing.T) { p := issueCommentTestPayload() - d := new(MSTeamsPayload) - pl, err := d.IssueComment(p) + pl, err := mc.IssueComment(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Equal(t, "more info needed", pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.Title) + assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Equal(t, "more info needed", pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repository.FullName, fact.Value) } else if fact.Name == "Issue #:" { @@ -209,27 +193,24 @@ func TestMSTeamsPayload(t *testing.T) { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", pl.PotentialAction[0].Targets[0].URI) }) t.Run("PullRequest", func(t *testing.T) { p := pullRequestTestPayload() - d := new(MSTeamsPayload) - pl, err := d.PullRequest(p) + pl, err := mc.PullRequest(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Equal(t, "fixes bug #2", pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.Title) + assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Equal(t, "fixes bug #2", pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repository.FullName, fact.Value) } else if fact.Name == "Pull request #:" { @@ -238,27 +219,24 @@ func TestMSTeamsPayload(t *testing.T) { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.PotentialAction[0].Targets[0].URI) }) t.Run("PullRequestComment", func(t *testing.T) { p := pullRequestCommentTestPayload() - d := new(MSTeamsPayload) - pl, err := d.IssueComment(p) + pl, err := mc.IssueComment(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Equal(t, "changes requested", pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.Title) + assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Equal(t, "changes requested", pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repository.FullName, fact.Value) } else if fact.Name == "Issue #:" { @@ -267,28 +245,25 @@ func TestMSTeamsPayload(t *testing.T) { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", pl.PotentialAction[0].Targets[0].URI) }) t.Run("Review", func(t *testing.T) { p := pullRequestTestPayload() p.Action = api.HookIssueReviewed - d := new(MSTeamsPayload) - pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved) + pl, err := mc.Review(p, webhook_module.HookEventPullRequestReviewApproved) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Equal(t, "good job", pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.Title) + assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Equal(t, "good job", pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repository.FullName, fact.Value) } else if fact.Name == "Pull request #:" { @@ -297,155 +272,139 @@ func TestMSTeamsPayload(t *testing.T) { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.PotentialAction[0].Targets[0].URI) }) t.Run("Repository", func(t *testing.T) { p := repositoryTestPayload() - d := new(MSTeamsPayload) - pl, err := d.Repository(p) + pl, err := mc.Repository(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo] Repository created", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo] Repository created", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 1) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo] Repository created", pl.Title) + assert.Equal(t, "[test/repo] Repository created", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Empty(t, pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 1) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repository.FullName, fact.Value) } else { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo", pl.PotentialAction[0].Targets[0].URI) }) t.Run("Package", func(t *testing.T) { p := packageTestPayload() - d := new(MSTeamsPayload) - pl, err := d.Package(p) + pl, err := mc.Package(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "Package created: GiteaContainer:latest", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "Package created: GiteaContainer:latest", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 1) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "Package created: GiteaContainer:latest", pl.Title) + assert.Equal(t, "Package created: GiteaContainer:latest", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Empty(t, pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 1) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Package:" { assert.Equal(t, p.Package.Name, fact.Value) } else { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", pl.PotentialAction[0].Targets[0].URI) }) t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() - d := new(MSTeamsPayload) p.Action = api.HookWikiCreated - pl, err := d.Wiki(p) + pl, err := mc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Equal(t, "", pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.Title) + assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Equal(t, "", pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repository.FullName, fact.Value) } else { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.PotentialAction[0].Targets[0].URI) p.Action = api.HookWikiEdited - pl, err = d.Wiki(p) + pl, err = mc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Equal(t, "", pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.Title) + assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Equal(t, "", pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repository.FullName, fact.Value) } else { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.PotentialAction[0].Targets[0].URI) p.Action = api.HookWikiDeleted - pl, err = d.Wiki(p) + pl, err = mc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.Title) + assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Empty(t, pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repository.FullName, fact.Value) } else { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.PotentialAction[0].Targets[0].URI) }) t.Run("Release", func(t *testing.T) { p := pullReleaseTestPayload() - d := new(MSTeamsPayload) - pl, err := d.Release(p) + pl, err := mc.Release(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - assert.Equal(t, "[test/repo] Release created: v1.0", pl.(*MSTeamsPayload).Title) - assert.Equal(t, "[test/repo] Release created: v1.0", pl.(*MSTeamsPayload).Summary) - assert.Len(t, pl.(*MSTeamsPayload).Sections, 1) - assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle) - assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text) - assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2) - for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts { + assert.Equal(t, "[test/repo] Release created: v1.0", pl.Title) + assert.Equal(t, "[test/repo] Release created: v1.0", pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + assert.Empty(t, pl.Sections[0].Text) + assert.Len(t, pl.Sections[0].Facts, 2) + for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { assert.Equal(t, p.Repository.FullName, fact.Value) } else if fact.Name == "Tag:" { @@ -454,21 +413,43 @@ func TestMSTeamsPayload(t *testing.T) { t.Fail() } } - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1) - assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI) + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", pl.PotentialAction[0].Targets[0].URI) }) } func TestMSTeamsJSONPayload(t *testing.T) { p := pushTestPayload() - - pl, err := new(MSTeamsPayload).Push(p) + data, err := p.JSONPayload() require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &MSTeamsPayload{}, pl) - json, err := pl.JSONPayload() + hook := &webhook_model.Webhook{ + RepoID: 3, + IsActive: true, + Type: webhook_module.MSTEAMS, + URL: "https://msteams.example.com/", + Meta: ``, + HTTPMethod: "POST", + } + task := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadContent: string(data), + PayloadVersion: 2, + } + + req, reqBody, err := newMSTeamsRequest(context.Background(), hook, task) + require.NotNil(t, req) + require.NotNil(t, reqBody) require.NoError(t, err) - assert.NotEmpty(t, json) + + assert.Equal(t, "POST", req.Method) + assert.Equal(t, "https://msteams.example.com/", req.URL.String()) + assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) + assert.Equal(t, "application/json", req.Header.Get("Content-Type")) + var body MSTeamsPayload + err = json.NewDecoder(req.Body).Decode(&body) + assert.NoError(t, err) + assert.Equal(t, "[test/repo:test] 2 new commits", body.Summary) } diff --git a/services/webhook/packagist.go b/services/webhook/packagist.go index 714a4c076e..7880d8b606 100644 --- a/services/webhook/packagist.go +++ b/services/webhook/packagist.go @@ -4,7 +4,9 @@ package webhook import ( - "errors" + "context" + "fmt" + "net/http" webhook_model "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/json" @@ -38,84 +40,85 @@ func GetPackagistHook(w *webhook_model.Webhook) *PackagistMeta { return s } -// JSONPayload Marshals the PackagistPayload to json -func (f *PackagistPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(f, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -var _ PayloadConvertor = &PackagistPayload{} - // Create implements PayloadConvertor Create method -func (f *PackagistPayload) Create(_ *api.CreatePayload) (api.Payloader, error) { - return nil, nil +func (pc packagistConvertor) Create(_ *api.CreatePayload) (PackagistPayload, error) { + return PackagistPayload{}, nil } // Delete implements PayloadConvertor Delete method -func (f *PackagistPayload) Delete(_ *api.DeletePayload) (api.Payloader, error) { - return nil, nil +func (pc packagistConvertor) Delete(_ *api.DeletePayload) (PackagistPayload, error) { + return PackagistPayload{}, nil } // Fork implements PayloadConvertor Fork method -func (f *PackagistPayload) Fork(_ *api.ForkPayload) (api.Payloader, error) { - return nil, nil +func (pc packagistConvertor) Fork(_ *api.ForkPayload) (PackagistPayload, error) { + return PackagistPayload{}, nil } // Push implements PayloadConvertor Push method -func (f *PackagistPayload) Push(_ *api.PushPayload) (api.Payloader, error) { - return f, nil +// https://packagist.org/about +func (pc packagistConvertor) Push(_ *api.PushPayload) (PackagistPayload, error) { + return PackagistPayload{ + PackagistRepository: struct { + URL string `json:"url"` + }{ + URL: pc.PackageURL, + }, + }, nil } // Issue implements PayloadConvertor Issue method -func (f *PackagistPayload) Issue(_ *api.IssuePayload) (api.Payloader, error) { - return nil, nil +func (pc packagistConvertor) Issue(_ *api.IssuePayload) (PackagistPayload, error) { + return PackagistPayload{}, nil } // IssueComment implements PayloadConvertor IssueComment method -func (f *PackagistPayload) IssueComment(_ *api.IssueCommentPayload) (api.Payloader, error) { - return nil, nil +func (pc packagistConvertor) IssueComment(_ *api.IssueCommentPayload) (PackagistPayload, error) { + return PackagistPayload{}, nil } // PullRequest implements PayloadConvertor PullRequest method -func (f *PackagistPayload) PullRequest(_ *api.PullRequestPayload) (api.Payloader, error) { - return nil, nil +func (pc packagistConvertor) PullRequest(_ *api.PullRequestPayload) (PackagistPayload, error) { + return PackagistPayload{}, nil } // Review implements PayloadConvertor Review method -func (f *PackagistPayload) Review(_ *api.PullRequestPayload, _ webhook_module.HookEventType) (api.Payloader, error) { - return nil, nil +func (pc packagistConvertor) Review(_ *api.PullRequestPayload, _ webhook_module.HookEventType) (PackagistPayload, error) { + return PackagistPayload{}, nil } // Repository implements PayloadConvertor Repository method -func (f *PackagistPayload) Repository(_ *api.RepositoryPayload) (api.Payloader, error) { - return nil, nil +func (pc packagistConvertor) Repository(_ *api.RepositoryPayload) (PackagistPayload, error) { + return PackagistPayload{}, nil } // Wiki implements PayloadConvertor Wiki method -func (f *PackagistPayload) Wiki(_ *api.WikiPayload) (api.Payloader, error) { - return nil, nil +func (pc packagistConvertor) Wiki(_ *api.WikiPayload) (PackagistPayload, error) { + return PackagistPayload{}, nil } // Release implements PayloadConvertor Release method -func (f *PackagistPayload) Release(_ *api.ReleasePayload) (api.Payloader, error) { - return nil, nil +func (pc packagistConvertor) Release(_ *api.ReleasePayload) (PackagistPayload, error) { + return PackagistPayload{}, nil } -func (f *PackagistPayload) Package(_ *api.PackagePayload) (api.Payloader, error) { - return nil, nil +func (pc packagistConvertor) Package(_ *api.PackagePayload) (PackagistPayload, error) { + return PackagistPayload{}, nil } -// GetPackagistPayload converts a packagist webhook into a PackagistPayload -func GetPackagistPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) { - s := new(PackagistPayload) +type packagistConvertor struct { + PackageURL string +} - packagist := &PackagistMeta{} - if err := json.Unmarshal([]byte(meta), &packagist); err != nil { - return s, errors.New("GetPackagistPayload meta json:" + err.Error()) +var _ payloadConvertor[PackagistPayload] = packagistConvertor{} + +func newPackagistRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { + meta := &PackagistMeta{} + if err := json.Unmarshal([]byte(w.Meta), meta); err != nil { + return nil, nil, fmt.Errorf("newpackagistRequest meta json: %w", err) } - s.PackagistRepository.URL = packagist.PackageURL - return convertPayloader(s, p, event) + pc := packagistConvertor{ + PackageURL: meta.PackageURL, + } + return newJSONRequest(pc, w, t, true) } diff --git a/services/webhook/packagist_test.go b/services/webhook/packagist_test.go index 26d01b0555..e9b0695baa 100644 --- a/services/webhook/packagist_test.go +++ b/services/webhook/packagist_test.go @@ -4,8 +4,11 @@ package webhook import ( + "context" "testing" + webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -14,155 +17,199 @@ import ( ) func TestPackagistPayload(t *testing.T) { + pc := packagistConvertor{ + PackageURL: "https://packagist.org/packages/example", + } t.Run("Create", func(t *testing.T) { p := createTestPayload() - d := new(PackagistPayload) - pl, err := d.Create(p) + pl, err := pc.Create(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) }) t.Run("Delete", func(t *testing.T) { p := deleteTestPayload() - d := new(PackagistPayload) - pl, err := d.Delete(p) + pl, err := pc.Delete(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) }) t.Run("Fork", func(t *testing.T) { p := forkTestPayload() - d := new(PackagistPayload) - pl, err := d.Fork(p) + pl, err := pc.Fork(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) }) t.Run("Push", func(t *testing.T) { p := pushTestPayload() - d := new(PackagistPayload) - d.PackagistRepository.URL = "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN" - pl, err := d.Push(p) + pl, err := pc.Push(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &PackagistPayload{}, pl) - assert.Equal(t, "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN", pl.(*PackagistPayload).PackagistRepository.URL) + assert.Equal(t, "https://packagist.org/packages/example", pl.PackagistRepository.URL) }) t.Run("Issue", func(t *testing.T) { p := issueTestPayload() - d := new(PackagistPayload) p.Action = api.HookIssueOpened - pl, err := d.Issue(p) + pl, err := pc.Issue(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) p.Action = api.HookIssueClosed - pl, err = d.Issue(p) + pl, err = pc.Issue(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) }) t.Run("IssueComment", func(t *testing.T) { p := issueCommentTestPayload() - d := new(PackagistPayload) - pl, err := d.IssueComment(p) + pl, err := pc.IssueComment(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) }) t.Run("PullRequest", func(t *testing.T) { p := pullRequestTestPayload() - d := new(PackagistPayload) - pl, err := d.PullRequest(p) + pl, err := pc.PullRequest(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) }) t.Run("PullRequestComment", func(t *testing.T) { p := pullRequestCommentTestPayload() - d := new(PackagistPayload) - pl, err := d.IssueComment(p) + pl, err := pc.IssueComment(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) }) t.Run("Review", func(t *testing.T) { p := pullRequestTestPayload() p.Action = api.HookIssueReviewed - d := new(PackagistPayload) - pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved) + pl, err := pc.Review(p, webhook_module.HookEventPullRequestReviewApproved) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) }) t.Run("Repository", func(t *testing.T) { p := repositoryTestPayload() - d := new(PackagistPayload) - pl, err := d.Repository(p) + pl, err := pc.Repository(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) }) t.Run("Package", func(t *testing.T) { p := packageTestPayload() - d := new(PackagistPayload) - pl, err := d.Package(p) + pl, err := pc.Package(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) }) t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() - d := new(PackagistPayload) p.Action = api.HookWikiCreated - pl, err := d.Wiki(p) + pl, err := pc.Wiki(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) p.Action = api.HookWikiEdited - pl, err = d.Wiki(p) + pl, err = pc.Wiki(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) p.Action = api.HookWikiDeleted - pl, err = d.Wiki(p) + pl, err = pc.Wiki(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) }) t.Run("Release", func(t *testing.T) { p := pullReleaseTestPayload() - d := new(PackagistPayload) - pl, err := d.Release(p) + pl, err := pc.Release(p) require.NoError(t, err) - require.Nil(t, pl) + require.Equal(t, pl, PackagistPayload{}) }) } func TestPackagistJSONPayload(t *testing.T) { p := pushTestPayload() - - pl, err := new(PackagistPayload).Push(p) + data, err := p.JSONPayload() require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &PackagistPayload{}, pl) - json, err := pl.JSONPayload() + hook := &webhook_model.Webhook{ + RepoID: 3, + IsActive: true, + Type: webhook_module.PACKAGIST, + URL: "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN", + Meta: `{"package_url":"https://packagist.org/packages/example"}`, + HTTPMethod: "POST", + } + task := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadContent: string(data), + PayloadVersion: 2, + } + + req, reqBody, err := newPackagistRequest(context.Background(), hook, task) + require.NotNil(t, req) + require.NotNil(t, reqBody) require.NoError(t, err) - assert.NotEmpty(t, json) + + assert.Equal(t, "POST", req.Method) + assert.Equal(t, "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN", req.URL.String()) + assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) + assert.Equal(t, "application/json", req.Header.Get("Content-Type")) + var body PackagistPayload + err = json.NewDecoder(req.Body).Decode(&body) + assert.NoError(t, err) + assert.Equal(t, "https://packagist.org/packages/example", body.PackagistRepository.URL) +} + +func TestPackagistEmptyPayload(t *testing.T) { + p := createTestPayload() + data, err := p.JSONPayload() + require.NoError(t, err) + + hook := &webhook_model.Webhook{ + RepoID: 3, + IsActive: true, + Type: webhook_module.PACKAGIST, + URL: "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN", + Meta: `{"package_url":"https://packagist.org/packages/example"}`, + HTTPMethod: "POST", + } + task := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventCreate, + PayloadContent: string(data), + PayloadVersion: 2, + } + + req, reqBody, err := newPackagistRequest(context.Background(), hook, task) + require.NotNil(t, req) + require.NotNil(t, reqBody) + require.NoError(t, err) + + assert.Equal(t, "POST", req.Method) + assert.Equal(t, "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN", req.URL.String()) + assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) + assert.Equal(t, "application/json", req.Header.Get("Content-Type")) + var body PackagistPayload + err = json.NewDecoder(req.Body).Decode(&body) + assert.NoError(t, err) + assert.Equal(t, "", body.PackagistRepository.URL) } diff --git a/services/webhook/payloader.go b/services/webhook/payloader.go index bd482c04ea..abf9946cca 100644 --- a/services/webhook/payloader.go +++ b/services/webhook/payloader.go @@ -4,58 +4,104 @@ package webhook import ( + "bytes" + "fmt" + "net/http" + + webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" ) -// PayloadConvertor defines the interface to convert system webhook payload to external payload -type PayloadConvertor interface { - api.Payloader - Create(*api.CreatePayload) (api.Payloader, error) - Delete(*api.DeletePayload) (api.Payloader, error) - Fork(*api.ForkPayload) (api.Payloader, error) - Issue(*api.IssuePayload) (api.Payloader, error) - IssueComment(*api.IssueCommentPayload) (api.Payloader, error) - Push(*api.PushPayload) (api.Payloader, error) - PullRequest(*api.PullRequestPayload) (api.Payloader, error) - Review(*api.PullRequestPayload, webhook_module.HookEventType) (api.Payloader, error) - Repository(*api.RepositoryPayload) (api.Payloader, error) - Release(*api.ReleasePayload) (api.Payloader, error) - Wiki(*api.WikiPayload) (api.Payloader, error) - Package(*api.PackagePayload) (api.Payloader, error) +// payloadConvertor defines the interface to convert system payload to webhook payload +type payloadConvertor[T any] interface { + Create(*api.CreatePayload) (T, error) + Delete(*api.DeletePayload) (T, error) + Fork(*api.ForkPayload) (T, error) + Issue(*api.IssuePayload) (T, error) + IssueComment(*api.IssueCommentPayload) (T, error) + Push(*api.PushPayload) (T, error) + PullRequest(*api.PullRequestPayload) (T, error) + Review(*api.PullRequestPayload, webhook_module.HookEventType) (T, error) + Repository(*api.RepositoryPayload) (T, error) + Release(*api.ReleasePayload) (T, error) + Wiki(*api.WikiPayload) (T, error) + Package(*api.PackagePayload) (T, error) } -func convertPayloader(s PayloadConvertor, p api.Payloader, event webhook_module.HookEventType) (api.Payloader, error) { +func convertUnmarshalledJSON[T, P any](convert func(P) (T, error), data []byte) (T, error) { + var p P + if err := json.Unmarshal(data, &p); err != nil { + var t T + return t, fmt.Errorf("could not unmarshal payload: %w", err) + } + return convert(p) +} + +func newPayload[T any](rc payloadConvertor[T], data []byte, event webhook_module.HookEventType) (T, error) { switch event { case webhook_module.HookEventCreate: - return s.Create(p.(*api.CreatePayload)) + return convertUnmarshalledJSON(rc.Create, data) case webhook_module.HookEventDelete: - return s.Delete(p.(*api.DeletePayload)) + return convertUnmarshalledJSON(rc.Delete, data) case webhook_module.HookEventFork: - return s.Fork(p.(*api.ForkPayload)) + return convertUnmarshalledJSON(rc.Fork, data) case webhook_module.HookEventIssues, webhook_module.HookEventIssueAssign, webhook_module.HookEventIssueLabel, webhook_module.HookEventIssueMilestone: - return s.Issue(p.(*api.IssuePayload)) + return convertUnmarshalledJSON(rc.Issue, data) case webhook_module.HookEventIssueComment, webhook_module.HookEventPullRequestComment: - pl, ok := p.(*api.IssueCommentPayload) - if ok { - return s.IssueComment(pl) - } - return s.PullRequest(p.(*api.PullRequestPayload)) + // previous code sometimes sent s.PullRequest(p.(*api.PullRequestPayload)) + // however I couldn't find in notifier.go such a payload with an HookEvent***Comment event + + // History (most recent first): + // - refactored in https://github.com/go-gitea/gitea/pull/12310 + // - assertion added in https://github.com/go-gitea/gitea/pull/12046 + // - issue raised in https://github.com/go-gitea/gitea/issues/11940#issuecomment-645713996 + // > That's because for HookEventPullRequestComment event, some places use IssueCommentPayload and others use PullRequestPayload + + // In modules/actions/workflows.go:183 the type assertion is always payload.(*api.IssueCommentPayload) + return convertUnmarshalledJSON(rc.IssueComment, data) case webhook_module.HookEventPush: - return s.Push(p.(*api.PushPayload)) + return convertUnmarshalledJSON(rc.Push, data) case webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequestAssign, webhook_module.HookEventPullRequestLabel, webhook_module.HookEventPullRequestMilestone, webhook_module.HookEventPullRequestSync, webhook_module.HookEventPullRequestReviewRequest: - return s.PullRequest(p.(*api.PullRequestPayload)) + return convertUnmarshalledJSON(rc.PullRequest, data) case webhook_module.HookEventPullRequestReviewApproved, webhook_module.HookEventPullRequestReviewRejected, webhook_module.HookEventPullRequestReviewComment: - return s.Review(p.(*api.PullRequestPayload), event) + return convertUnmarshalledJSON(func(p *api.PullRequestPayload) (T, error) { + return rc.Review(p, event) + }, data) case webhook_module.HookEventRepository: - return s.Repository(p.(*api.RepositoryPayload)) + return convertUnmarshalledJSON(rc.Repository, data) case webhook_module.HookEventRelease: - return s.Release(p.(*api.ReleasePayload)) + return convertUnmarshalledJSON(rc.Release, data) case webhook_module.HookEventWiki: - return s.Wiki(p.(*api.WikiPayload)) + return convertUnmarshalledJSON(rc.Wiki, data) case webhook_module.HookEventPackage: - return s.Package(p.(*api.PackagePayload)) + return convertUnmarshalledJSON(rc.Package, data) } - return s, nil + var t T + return t, fmt.Errorf("newPayload unsupported event: %s", event) +} + +func newJSONRequest[T any](pc payloadConvertor[T], w *webhook_model.Webhook, t *webhook_model.HookTask, withDefaultHeaders bool) (*http.Request, []byte, error) { + payload, err := newPayload(pc, []byte(t.PayloadContent), t.EventType) + if err != nil { + return nil, nil, err + } + + body, err := json.MarshalIndent(payload, "", " ") + if err != nil { + return nil, nil, err + } + + req, err := http.NewRequest(w.HTTPMethod, w.URL, bytes.NewReader(body)) + if err != nil { + return nil, nil, err + } + req.Header.Set("Content-Type", "application/json") + + if withDefaultHeaders { + return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body) + } + return req, body, nil } diff --git a/services/webhook/slack.go b/services/webhook/slack.go index 945b0662d8..ba8bac27d9 100644 --- a/services/webhook/slack.go +++ b/services/webhook/slack.go @@ -4,8 +4,9 @@ package webhook import ( - "errors" + "context" "fmt" + "net/http" "regexp" "strings" @@ -39,7 +40,6 @@ func GetSlackHook(w *webhook_model.Webhook) *SlackMeta { type SlackPayload struct { Channel string `json:"channel"` Text string `json:"text"` - Color string `json:"-"` Username string `json:"username"` IconURL string `json:"icon_url"` UnfurlLinks int `json:"unfurl_links"` @@ -56,15 +56,6 @@ type SlackAttachment struct { Text string `json:"text"` } -// JSONPayload Marshals the SlackPayload to json -func (s *SlackPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(s, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - // SlackTextFormatter replaces &, <, > with HTML characters // see: https://api.slack.com/docs/formatting func SlackTextFormatter(s string) string { @@ -98,10 +89,8 @@ func SlackLinkToRef(repoURL, ref string) string { return SlackLinkFormatter(url, refName) } -var _ PayloadConvertor = &SlackPayload{} - -// Create implements PayloadConvertor Create method -func (s *SlackPayload) Create(p *api.CreatePayload) (api.Payloader, error) { +// Create implements payloadConvertor Create method +func (s slackConvertor) Create(p *api.CreatePayload) (SlackPayload, error) { repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) refLink := SlackLinkToRef(p.Repo.HTMLURL, p.Ref) text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName) @@ -110,7 +99,7 @@ func (s *SlackPayload) Create(p *api.CreatePayload) (api.Payloader, error) { } // Delete composes Slack payload for delete a branch or tag. -func (s *SlackPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { +func (s slackConvertor) Delete(p *api.DeletePayload) (SlackPayload, error) { refName := git.RefName(p.Ref).ShortName() repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName) @@ -119,7 +108,7 @@ func (s *SlackPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { } // Fork composes Slack payload for forked by a repository. -func (s *SlackPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { +func (s slackConvertor) Fork(p *api.ForkPayload) (SlackPayload, error) { baseLink := SlackLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) forkLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink) @@ -127,8 +116,8 @@ func (s *SlackPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { return s.createPayload(text, nil), nil } -// Issue implements PayloadConvertor Issue method -func (s *SlackPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { +// Issue implements payloadConvertor Issue method +func (s slackConvertor) Issue(p *api.IssuePayload) (SlackPayload, error) { text, issueTitle, attachmentText, color := getIssuesPayloadInfo(p, SlackLinkFormatter, true) var attachments []SlackAttachment @@ -146,8 +135,8 @@ func (s *SlackPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { return s.createPayload(text, attachments), nil } -// IssueComment implements PayloadConvertor IssueComment method -func (s *SlackPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { +// IssueComment implements payloadConvertor IssueComment method +func (s slackConvertor) IssueComment(p *api.IssueCommentPayload) (SlackPayload, error) { text, issueTitle, color := getIssueCommentPayloadInfo(p, SlackLinkFormatter, true) return s.createPayload(text, []SlackAttachment{{ @@ -158,28 +147,28 @@ func (s *SlackPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, }}), nil } -// Wiki implements PayloadConvertor Wiki method -func (s *SlackPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) { +// Wiki implements payloadConvertor Wiki method +func (s slackConvertor) Wiki(p *api.WikiPayload) (SlackPayload, error) { text, _, _ := getWikiPayloadInfo(p, SlackLinkFormatter, true) return s.createPayload(text, nil), nil } -// Release implements PayloadConvertor Release method -func (s *SlackPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { +// Release implements payloadConvertor Release method +func (s slackConvertor) Release(p *api.ReleasePayload) (SlackPayload, error) { text, _ := getReleasePayloadInfo(p, SlackLinkFormatter, true) return s.createPayload(text, nil), nil } -func (s *SlackPayload) Package(p *api.PackagePayload) (api.Payloader, error) { +func (s slackConvertor) Package(p *api.PackagePayload) (SlackPayload, error) { text, _ := getPackagePayloadInfo(p, SlackLinkFormatter, true) return s.createPayload(text, nil), nil } -// Push implements PayloadConvertor Push method -func (s *SlackPayload) Push(p *api.PushPayload) (api.Payloader, error) { +// Push implements payloadConvertor Push method +func (s slackConvertor) Push(p *api.PushPayload) (SlackPayload, error) { // n new commits var ( commitDesc string @@ -219,8 +208,8 @@ func (s *SlackPayload) Push(p *api.PushPayload) (api.Payloader, error) { }}), nil } -// PullRequest implements PayloadConvertor PullRequest method -func (s *SlackPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { +// PullRequest implements payloadConvertor PullRequest method +func (s slackConvertor) PullRequest(p *api.PullRequestPayload) (SlackPayload, error) { text, issueTitle, attachmentText, color := getPullRequestPayloadInfo(p, SlackLinkFormatter, true) var attachments []SlackAttachment @@ -238,8 +227,8 @@ func (s *SlackPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, er return s.createPayload(text, attachments), nil } -// Review implements PayloadConvertor Review method -func (s *SlackPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) { +// Review implements payloadConvertor Review method +func (s slackConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (SlackPayload, error) { senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index) @@ -250,7 +239,7 @@ func (s *SlackPayload) Review(p *api.PullRequestPayload, event webhook_module.Ho case api.HookIssueReviewed: action, err := parseHookPullRequestEventType(event) if err != nil { - return nil, err + return SlackPayload{}, err } text = fmt.Sprintf("[%s] Pull request review %s: [%s](%s) by %s", repoLink, action, title, titleLink, senderLink) @@ -259,8 +248,8 @@ func (s *SlackPayload) Review(p *api.PullRequestPayload, event webhook_module.Ho return s.createPayload(text, nil), nil } -// Repository implements PayloadConvertor Repository method -func (s *SlackPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { +// Repository implements payloadConvertor Repository method +func (s slackConvertor) Repository(p *api.RepositoryPayload) (SlackPayload, error) { senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) var text string @@ -275,8 +264,8 @@ func (s *SlackPayload) Repository(p *api.RepositoryPayload) (api.Payloader, erro return s.createPayload(text, nil), nil } -func (s *SlackPayload) createPayload(text string, attachments []SlackAttachment) *SlackPayload { - return &SlackPayload{ +func (s slackConvertor) createPayload(text string, attachments []SlackAttachment) SlackPayload { + return SlackPayload{ Channel: s.Channel, Text: text, Username: s.Username, @@ -285,21 +274,27 @@ func (s *SlackPayload) createPayload(text string, attachments []SlackAttachment) } } -// GetSlackPayload converts a slack webhook into a SlackPayload -func GetSlackPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) { - s := new(SlackPayload) +type slackConvertor struct { + Channel string + Username string + IconURL string + Color string +} - slack := &SlackMeta{} - if err := json.Unmarshal([]byte(meta), &slack); err != nil { - return s, errors.New("GetSlackPayload meta json:" + err.Error()) +var _ payloadConvertor[SlackPayload] = slackConvertor{} + +func newSlackRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { + meta := &SlackMeta{} + if err := json.Unmarshal([]byte(w.Meta), meta); err != nil { + return nil, nil, fmt.Errorf("newSlackRequest meta json: %w", err) } - - s.Channel = slack.Channel - s.Username = slack.Username - s.IconURL = slack.IconURL - s.Color = slack.Color - - return convertPayloader(s, p, event) + sc := slackConvertor{ + Channel: meta.Channel, + Username: meta.Username, + IconURL: meta.IconURL, + Color: meta.Color, + } + return newJSONRequest(sc, w, t, true) } var slackChannel = regexp.MustCompile(`^#?[a-z0-9_-]{1,80}$`) diff --git a/services/webhook/slack_test.go b/services/webhook/slack_test.go index b1340963e2..7ebf16aba2 100644 --- a/services/webhook/slack_test.go +++ b/services/webhook/slack_test.go @@ -4,8 +4,11 @@ package webhook import ( + "context" "testing" + webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -14,201 +17,180 @@ import ( ) func TestSlackPayload(t *testing.T) { + sc := slackConvertor{} + t.Run("Create", func(t *testing.T) { p := createTestPayload() - d := new(SlackPayload) - pl, err := d.Create(p) + pl, err := sc.Create(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>:<http://localhost:3000/test/repo/src/branch/test|test>] branch created by user1", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>:<http://localhost:3000/test/repo/src/branch/test|test>] branch created by user1", pl.Text) }) t.Run("Delete", func(t *testing.T) { p := deleteTestPayload() - d := new(SlackPayload) - pl, err := d.Delete(p) + pl, err := sc.Delete(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>:test] branch deleted by user1", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>:test] branch deleted by user1", pl.Text) }) t.Run("Fork", func(t *testing.T) { p := forkTestPayload() - d := new(SlackPayload) - pl, err := d.Fork(p) + pl, err := sc.Fork(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "<http://localhost:3000/test/repo2|test/repo2> is forked to <http://localhost:3000/test/repo|test/repo>", pl.(*SlackPayload).Text) + assert.Equal(t, "<http://localhost:3000/test/repo2|test/repo2> is forked to <http://localhost:3000/test/repo|test/repo>", pl.Text) }) t.Run("Push", func(t *testing.T) { p := pushTestPayload() - d := new(SlackPayload) - pl, err := d.Push(p) + pl, err := sc.Push(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>:<http://localhost:3000/test/repo/src/branch/test|test>] 2 new commits pushed by user1", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>:<http://localhost:3000/test/repo/src/branch/test|test>] 2 new commits pushed by user1", pl.Text) }) t.Run("Issue", func(t *testing.T) { p := issueTestPayload() - d := new(SlackPayload) p.Action = api.HookIssueOpened - pl, err := d.Issue(p) + pl, err := sc.Issue(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Issue opened: <http://localhost:3000/test/repo/issues/2|#2 crash> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Issue opened: <http://localhost:3000/test/repo/issues/2|#2 crash> by <https://try.gitea.io/user1|user1>", pl.Text) p.Action = api.HookIssueClosed - pl, err = d.Issue(p) + pl, err = sc.Issue(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Issue closed: <http://localhost:3000/test/repo/issues/2|#2 crash> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Issue closed: <http://localhost:3000/test/repo/issues/2|#2 crash> by <https://try.gitea.io/user1|user1>", pl.Text) }) t.Run("IssueComment", func(t *testing.T) { p := issueCommentTestPayload() - d := new(SlackPayload) - pl, err := d.IssueComment(p) + pl, err := sc.IssueComment(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] New comment on issue <http://localhost:3000/test/repo/issues/2|#2 crash> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] New comment on issue <http://localhost:3000/test/repo/issues/2|#2 crash> by <https://try.gitea.io/user1|user1>", pl.Text) }) t.Run("PullRequest", func(t *testing.T) { p := pullRequestTestPayload() - d := new(SlackPayload) - pl, err := d.PullRequest(p) + pl, err := sc.PullRequest(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Pull request opened: <http://localhost:3000/test/repo/pulls/12|#12 Fix bug> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Pull request opened: <http://localhost:3000/test/repo/pulls/12|#12 Fix bug> by <https://try.gitea.io/user1|user1>", pl.Text) }) t.Run("PullRequestComment", func(t *testing.T) { p := pullRequestCommentTestPayload() - d := new(SlackPayload) - pl, err := d.IssueComment(p) + pl, err := sc.IssueComment(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] New comment on pull request <http://localhost:3000/test/repo/pulls/12|#12 Fix bug> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] New comment on pull request <http://localhost:3000/test/repo/pulls/12|#12 Fix bug> by <https://try.gitea.io/user1|user1>", pl.Text) }) t.Run("Review", func(t *testing.T) { p := pullRequestTestPayload() p.Action = api.HookIssueReviewed - d := new(SlackPayload) - pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved) + pl, err := sc.Review(p, webhook_module.HookEventPullRequestReviewApproved) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Pull request review approved: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Pull request review approved: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by <https://try.gitea.io/user1|user1>", pl.Text) }) t.Run("Repository", func(t *testing.T) { p := repositoryTestPayload() - d := new(SlackPayload) - pl, err := d.Repository(p) + pl, err := sc.Repository(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Repository created by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Repository created by <https://try.gitea.io/user1|user1>", pl.Text) }) t.Run("Package", func(t *testing.T) { p := packageTestPayload() - d := new(SlackPayload) - pl, err := d.Package(p) + pl, err := sc.Package(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "Package created: <http://localhost:3000/user1/-/packages/container/GiteaContainer/latest|GiteaContainer:latest> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) + assert.Equal(t, "Package created: <http://localhost:3000/user1/-/packages/container/GiteaContainer/latest|GiteaContainer:latest> by <https://try.gitea.io/user1|user1>", pl.Text) }) t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() - d := new(SlackPayload) p.Action = api.HookWikiCreated - pl, err := d.Wiki(p) + pl, err := sc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] New wiki page '<http://localhost:3000/test/repo/wiki/index|index>' (Wiki change comment) by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] New wiki page '<http://localhost:3000/test/repo/wiki/index|index>' (Wiki change comment) by <https://try.gitea.io/user1|user1>", pl.Text) p.Action = api.HookWikiEdited - pl, err = d.Wiki(p) + pl, err = sc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Wiki page '<http://localhost:3000/test/repo/wiki/index|index>' edited (Wiki change comment) by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Wiki page '<http://localhost:3000/test/repo/wiki/index|index>' edited (Wiki change comment) by <https://try.gitea.io/user1|user1>", pl.Text) p.Action = api.HookWikiDeleted - pl, err = d.Wiki(p) + pl, err = sc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Wiki page '<http://localhost:3000/test/repo/wiki/index|index>' deleted by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Wiki page '<http://localhost:3000/test/repo/wiki/index|index>' deleted by <https://try.gitea.io/user1|user1>", pl.Text) }) t.Run("Release", func(t *testing.T) { p := pullReleaseTestPayload() - d := new(SlackPayload) - pl, err := d.Release(p) + pl, err := sc.Release(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Release created: <http://localhost:3000/test/repo/releases/tag/v1.0|v1.0> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Release created: <http://localhost:3000/test/repo/releases/tag/v1.0|v1.0> by <https://try.gitea.io/user1|user1>", pl.Text) }) } func TestSlackJSONPayload(t *testing.T) { p := pushTestPayload() - - pl, err := new(SlackPayload).Push(p) + data, err := p.JSONPayload() require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &SlackPayload{}, pl) - json, err := pl.JSONPayload() + hook := &webhook_model.Webhook{ + RepoID: 3, + IsActive: true, + Type: webhook_module.SLACK, + URL: "https://slack.example.com/", + Meta: `{}`, + HTTPMethod: "POST", + } + task := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadContent: string(data), + PayloadVersion: 2, + } + + req, reqBody, err := newSlackRequest(context.Background(), hook, task) + require.NotNil(t, req) + require.NotNil(t, reqBody) require.NoError(t, err) - assert.NotEmpty(t, json) + + assert.Equal(t, "POST", req.Method) + assert.Equal(t, "https://slack.example.com/", req.URL.String()) + assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) + assert.Equal(t, "application/json", req.Header.Get("Content-Type")) + var body SlackPayload + err = json.NewDecoder(req.Body).Decode(&body) + assert.NoError(t, err) + assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>:<http://localhost:3000/test/repo/src/branch/test|test>] 2 new commits pushed by user1", body.Text) } func TestIsValidSlackChannel(t *testing.T) { diff --git a/services/webhook/telegram.go b/services/webhook/telegram.go index 1bdc74e183..e4a5b5a424 100644 --- a/services/webhook/telegram.go +++ b/services/webhook/telegram.go @@ -4,14 +4,15 @@ package webhook import ( + "context" "fmt" + "net/http" "strings" webhook_model "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" ) @@ -41,22 +42,8 @@ func GetTelegramHook(w *webhook_model.Webhook) *TelegramMeta { return s } -var _ PayloadConvertor = &TelegramPayload{} - -// JSONPayload Marshals the TelegramPayload to json -func (t *TelegramPayload) JSONPayload() ([]byte, error) { - t.ParseMode = "HTML" - t.DisableWebPreview = true - t.Message = markup.Sanitize(t.Message) - data, err := json.MarshalIndent(t, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - // Create implements PayloadConvertor Create method -func (t *TelegramPayload) Create(p *api.CreatePayload) (api.Payloader, error) { +func (t telegramConvertor) Create(p *api.CreatePayload) (TelegramPayload, error) { // created tag/branch refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf(`[<a href="%s">%s</a>] %s <a href="%s">%s</a> created`, p.Repo.HTMLURL, p.Repo.FullName, p.RefType, @@ -66,7 +53,7 @@ func (t *TelegramPayload) Create(p *api.CreatePayload) (api.Payloader, error) { } // Delete implements PayloadConvertor Delete method -func (t *TelegramPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { +func (t telegramConvertor) Delete(p *api.DeletePayload) (TelegramPayload, error) { // created tag/branch refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf(`[<a href="%s">%s</a>] %s <a href="%s">%s</a> deleted`, p.Repo.HTMLURL, p.Repo.FullName, p.RefType, @@ -76,14 +63,14 @@ func (t *TelegramPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { } // Fork implements PayloadConvertor Fork method -func (t *TelegramPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { +func (t telegramConvertor) Fork(p *api.ForkPayload) (TelegramPayload, error) { title := fmt.Sprintf(`%s is forked to <a href="%s">%s</a>`, p.Forkee.FullName, p.Repo.HTMLURL, p.Repo.FullName) return createTelegramPayload(title), nil } // Push implements PayloadConvertor Push method -func (t *TelegramPayload) Push(p *api.PushPayload) (api.Payloader, error) { +func (t telegramConvertor) Push(p *api.PushPayload) (TelegramPayload, error) { var ( branchName = git.RefName(p.Ref).ShortName() commitDesc string @@ -121,34 +108,34 @@ func (t *TelegramPayload) Push(p *api.PushPayload) (api.Payloader, error) { } // Issue implements PayloadConvertor Issue method -func (t *TelegramPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { +func (t telegramConvertor) Issue(p *api.IssuePayload) (TelegramPayload, error) { text, _, attachmentText, _ := getIssuesPayloadInfo(p, htmlLinkFormatter, true) return createTelegramPayload(text + "\n\n" + attachmentText), nil } // IssueComment implements PayloadConvertor IssueComment method -func (t *TelegramPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { +func (t telegramConvertor) IssueComment(p *api.IssueCommentPayload) (TelegramPayload, error) { text, _, _ := getIssueCommentPayloadInfo(p, htmlLinkFormatter, true) return createTelegramPayload(text + "\n" + p.Comment.Body), nil } // PullRequest implements PayloadConvertor PullRequest method -func (t *TelegramPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { +func (t telegramConvertor) PullRequest(p *api.PullRequestPayload) (TelegramPayload, error) { text, _, attachmentText, _ := getPullRequestPayloadInfo(p, htmlLinkFormatter, true) return createTelegramPayload(text + "\n" + attachmentText), nil } // Review implements PayloadConvertor Review method -func (t *TelegramPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) { +func (t telegramConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (TelegramPayload, error) { var text, attachmentText string switch p.Action { case api.HookIssueReviewed: action, err := parseHookPullRequestEventType(event) if err != nil { - return nil, err + return TelegramPayload{}, err } text = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title) @@ -159,7 +146,7 @@ func (t *TelegramPayload) Review(p *api.PullRequestPayload, event webhook_module } // Repository implements PayloadConvertor Repository method -func (t *TelegramPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { +func (t telegramConvertor) Repository(p *api.RepositoryPayload) (TelegramPayload, error) { var title string switch p.Action { case api.HookRepoCreated: @@ -169,36 +156,39 @@ func (t *TelegramPayload) Repository(p *api.RepositoryPayload) (api.Payloader, e title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName) return createTelegramPayload(title), nil } - return nil, nil + return TelegramPayload{}, nil } // Wiki implements PayloadConvertor Wiki method -func (t *TelegramPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) { +func (t telegramConvertor) Wiki(p *api.WikiPayload) (TelegramPayload, error) { text, _, _ := getWikiPayloadInfo(p, htmlLinkFormatter, true) return createTelegramPayload(text), nil } // Release implements PayloadConvertor Release method -func (t *TelegramPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { +func (t telegramConvertor) Release(p *api.ReleasePayload) (TelegramPayload, error) { text, _ := getReleasePayloadInfo(p, htmlLinkFormatter, true) return createTelegramPayload(text), nil } -func (t *TelegramPayload) Package(p *api.PackagePayload) (api.Payloader, error) { +func (t telegramConvertor) Package(p *api.PackagePayload) (TelegramPayload, error) { text, _ := getPackagePayloadInfo(p, htmlLinkFormatter, true) return createTelegramPayload(text), nil } -// GetTelegramPayload converts a telegram webhook into a TelegramPayload -func GetTelegramPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) { - return convertPayloader(new(TelegramPayload), p, event) -} - -func createTelegramPayload(message string) *TelegramPayload { - return &TelegramPayload{ +func createTelegramPayload(message string) TelegramPayload { + return TelegramPayload{ Message: strings.TrimSpace(message), } } + +type telegramConvertor struct{} + +var _ payloadConvertor[TelegramPayload] = telegramConvertor{} + +func newTelegramRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { + return newJSONRequest(telegramConvertor{}, w, t, true) +} diff --git a/services/webhook/telegram_test.go b/services/webhook/telegram_test.go index 5b9927d057..27ab96cd09 100644 --- a/services/webhook/telegram_test.go +++ b/services/webhook/telegram_test.go @@ -4,8 +4,11 @@ package webhook import ( + "context" "testing" + webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -14,199 +17,177 @@ import ( ) func TestTelegramPayload(t *testing.T) { + tc := telegramConvertor{} t.Run("Create", func(t *testing.T) { p := createTestPayload() - d := new(TelegramPayload) - pl, err := d.Create(p) + pl, err := tc.Create(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test">test</a> created`, pl.(*TelegramPayload).Message) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test">test</a> created`, pl.Message) }) t.Run("Delete", func(t *testing.T) { p := deleteTestPayload() - d := new(TelegramPayload) - pl, err := d.Delete(p) + pl, err := tc.Delete(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test">test</a> deleted`, pl.(*TelegramPayload).Message) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test">test</a> deleted`, pl.Message) }) t.Run("Fork", func(t *testing.T) { p := forkTestPayload() - d := new(TelegramPayload) - pl, err := d.Fork(p) + pl, err := tc.Fork(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, `test/repo2 is forked to <a href="http://localhost:3000/test/repo">test/repo</a>`, pl.(*TelegramPayload).Message) + assert.Equal(t, `test/repo2 is forked to <a href="http://localhost:3000/test/repo">test/repo</a>`, pl.Message) }) t.Run("Push", func(t *testing.T) { p := pushTestPayload() - d := new(TelegramPayload) - pl, err := d.Push(p) + pl, err := tc.Push(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>:<a href=\"http://localhost:3000/test/repo/src/test\">test</a>] 2 new commits\n[<a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>] commit message - user1\n[<a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>] commit message - user1", pl.(*TelegramPayload).Message) + assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>:<a href=\"http://localhost:3000/test/repo/src/test\">test</a>] 2 new commits\n[<a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>] commit message - user1\n[<a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>] commit message - user1", pl.Message) }) t.Run("Issue", func(t *testing.T) { p := issueTestPayload() - d := new(TelegramPayload) p.Action = api.HookIssueOpened - pl, err := d.Issue(p) + pl, err := tc.Issue(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] Issue opened: <a href=\"http://localhost:3000/test/repo/issues/2\">#2 crash</a> by <a href=\"https://try.gitea.io/user1\">user1</a>\n\nissue body", pl.(*TelegramPayload).Message) + assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] Issue opened: <a href=\"http://localhost:3000/test/repo/issues/2\">#2 crash</a> by <a href=\"https://try.gitea.io/user1\">user1</a>\n\nissue body", pl.Message) p.Action = api.HookIssueClosed - pl, err = d.Issue(p) + pl, err = tc.Issue(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Issue closed: <a href="http://localhost:3000/test/repo/issues/2">#2 crash</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*TelegramPayload).Message) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Issue closed: <a href="http://localhost:3000/test/repo/issues/2">#2 crash</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.Message) }) t.Run("IssueComment", func(t *testing.T) { p := issueCommentTestPayload() - d := new(TelegramPayload) - pl, err := d.IssueComment(p) + pl, err := tc.IssueComment(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] New comment on issue <a href=\"http://localhost:3000/test/repo/issues/2\">#2 crash</a> by <a href=\"https://try.gitea.io/user1\">user1</a>\nmore info needed", pl.(*TelegramPayload).Message) + assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] New comment on issue <a href=\"http://localhost:3000/test/repo/issues/2\">#2 crash</a> by <a href=\"https://try.gitea.io/user1\">user1</a>\nmore info needed", pl.Message) }) t.Run("PullRequest", func(t *testing.T) { p := pullRequestTestPayload() - d := new(TelegramPayload) - pl, err := d.PullRequest(p) + pl, err := tc.PullRequest(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] Pull request opened: <a href=\"http://localhost:3000/test/repo/pulls/12\">#12 Fix bug</a> by <a href=\"https://try.gitea.io/user1\">user1</a>\nfixes bug #2", pl.(*TelegramPayload).Message) + assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] Pull request opened: <a href=\"http://localhost:3000/test/repo/pulls/12\">#12 Fix bug</a> by <a href=\"https://try.gitea.io/user1\">user1</a>\nfixes bug #2", pl.Message) }) t.Run("PullRequestComment", func(t *testing.T) { p := pullRequestCommentTestPayload() - d := new(TelegramPayload) - pl, err := d.IssueComment(p) + pl, err := tc.IssueComment(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] New comment on pull request <a href=\"http://localhost:3000/test/repo/pulls/12\">#12 Fix bug</a> by <a href=\"https://try.gitea.io/user1\">user1</a>\nchanges requested", pl.(*TelegramPayload).Message) + assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] New comment on pull request <a href=\"http://localhost:3000/test/repo/pulls/12\">#12 Fix bug</a> by <a href=\"https://try.gitea.io/user1\">user1</a>\nchanges requested", pl.Message) }) t.Run("Review", func(t *testing.T) { p := pullRequestTestPayload() p.Action = api.HookIssueReviewed - d := new(TelegramPayload) - pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved) + pl, err := tc.Review(p, webhook_module.HookEventPullRequestReviewApproved) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug\ngood job", pl.(*TelegramPayload).Message) + assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug\ngood job", pl.Message) }) t.Run("Repository", func(t *testing.T) { p := repositoryTestPayload() - d := new(TelegramPayload) - pl, err := d.Repository(p) + pl, err := tc.Repository(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Repository created`, pl.(*TelegramPayload).Message) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Repository created`, pl.Message) }) t.Run("Package", func(t *testing.T) { p := packageTestPayload() - d := new(TelegramPayload) - pl, err := d.Package(p) + pl, err := tc.Package(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, `Package created: <a href="http://localhost:3000/user1/-/packages/container/GiteaContainer/latest">GiteaContainer:latest</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*TelegramPayload).Message) + assert.Equal(t, `Package created: <a href="http://localhost:3000/user1/-/packages/container/GiteaContainer/latest">GiteaContainer:latest</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.Message) }) t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() - d := new(TelegramPayload) p.Action = api.HookWikiCreated - pl, err := d.Wiki(p) + pl, err := tc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] New wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' (Wiki change comment) by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*TelegramPayload).Message) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] New wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' (Wiki change comment) by <a href="https://try.gitea.io/user1">user1</a>`, pl.Message) p.Action = api.HookWikiEdited - pl, err = d.Wiki(p) + pl, err = tc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' edited (Wiki change comment) by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*TelegramPayload).Message) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' edited (Wiki change comment) by <a href="https://try.gitea.io/user1">user1</a>`, pl.Message) p.Action = api.HookWikiDeleted - pl, err = d.Wiki(p) + pl, err = tc.Wiki(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' deleted by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*TelegramPayload).Message) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' deleted by <a href="https://try.gitea.io/user1">user1</a>`, pl.Message) }) t.Run("Release", func(t *testing.T) { p := pullReleaseTestPayload() - d := new(TelegramPayload) - pl, err := d.Release(p) + pl, err := tc.Release(p) require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Release created: <a href="http://localhost:3000/test/repo/releases/tag/v1.0">v1.0</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*TelegramPayload).Message) + assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Release created: <a href="http://localhost:3000/test/repo/releases/tag/v1.0">v1.0</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.Message) }) } func TestTelegramJSONPayload(t *testing.T) { p := pushTestPayload() - - pl, err := new(TelegramPayload).Push(p) + data, err := p.JSONPayload() require.NoError(t, err) - require.NotNil(t, pl) - require.IsType(t, &TelegramPayload{}, pl) - json, err := pl.JSONPayload() + hook := &webhook_model.Webhook{ + RepoID: 3, + IsActive: true, + Type: webhook_module.TELEGRAM, + URL: "https://telegram.example.com/", + Meta: ``, + HTTPMethod: "POST", + } + task := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadContent: string(data), + PayloadVersion: 2, + } + + req, reqBody, err := newTelegramRequest(context.Background(), hook, task) + require.NotNil(t, req) + require.NotNil(t, reqBody) require.NoError(t, err) - assert.NotEmpty(t, json) + + assert.Equal(t, "POST", req.Method) + assert.Equal(t, "https://telegram.example.com/", req.URL.String()) + assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) + assert.Equal(t, "application/json", req.Header.Get("Content-Type")) + var body TelegramPayload + err = json.NewDecoder(req.Body).Decode(&body) + assert.NoError(t, err) + assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>:<a href=\"http://localhost:3000/test/repo/src/test\">test</a>] 2 new commits\n[<a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>] commit message - user1\n[<a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>] commit message - user1", body.Message) } diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go index 996942d1e5..e6646501da 100644 --- a/services/webhook/webhook.go +++ b/services/webhook/webhook.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "net/http" "strings" "code.gitea.io/gitea/models/db" @@ -26,48 +27,16 @@ import ( "github.com/gobwas/glob" ) -type webhook struct { - name webhook_module.HookType - payloadCreator func(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) -} - -var webhooks = map[webhook_module.HookType]*webhook{ - webhook_module.SLACK: { - name: webhook_module.SLACK, - payloadCreator: GetSlackPayload, - }, - webhook_module.DISCORD: { - name: webhook_module.DISCORD, - payloadCreator: GetDiscordPayload, - }, - webhook_module.DINGTALK: { - name: webhook_module.DINGTALK, - payloadCreator: GetDingtalkPayload, - }, - webhook_module.TELEGRAM: { - name: webhook_module.TELEGRAM, - payloadCreator: GetTelegramPayload, - }, - webhook_module.MSTEAMS: { - name: webhook_module.MSTEAMS, - payloadCreator: GetMSTeamsPayload, - }, - webhook_module.FEISHU: { - name: webhook_module.FEISHU, - payloadCreator: GetFeishuPayload, - }, - webhook_module.MATRIX: { - name: webhook_module.MATRIX, - payloadCreator: GetMatrixPayload, - }, - webhook_module.WECHATWORK: { - name: webhook_module.WECHATWORK, - payloadCreator: GetWechatworkPayload, - }, - webhook_module.PACKAGIST: { - name: webhook_module.PACKAGIST, - payloadCreator: GetPackagistPayload, - }, +var webhookRequesters = map[webhook_module.HookType]func(context.Context, *webhook_model.Webhook, *webhook_model.HookTask) (req *http.Request, body []byte, err error){ + webhook_module.SLACK: newSlackRequest, + webhook_module.DISCORD: newDiscordRequest, + webhook_module.DINGTALK: newDingtalkRequest, + webhook_module.TELEGRAM: newTelegramRequest, + webhook_module.MSTEAMS: newMSTeamsRequest, + webhook_module.FEISHU: newFeishuRequest, + webhook_module.MATRIX: newMatrixRequest, + webhook_module.WECHATWORK: newWechatworkRequest, + webhook_module.PACKAGIST: newPackagistRequest, } // IsValidHookTaskType returns true if a webhook registered @@ -75,7 +44,7 @@ func IsValidHookTaskType(name string) bool { if name == webhook_module.FORGEJO || name == webhook_module.GITEA || name == webhook_module.GOGS { return true } - _, ok := webhooks[name] + _, ok := webhookRequesters[name] return ok } @@ -159,7 +128,9 @@ func checkBranch(w *webhook_model.Webhook, branch string) bool { return g.Match(branch) } -// PrepareWebhook creates a hook task and enqueues it for processing +// PrepareWebhook creates a hook task and enqueues it for processing. +// The payload is saved as-is. The adjustments depending on the webhook type happen +// right before delivery, in the [Deliver] method. func PrepareWebhook(ctx context.Context, w *webhook_model.Webhook, event webhook_module.HookEventType, p api.Payloader) error { // Skip sending if webhooks are disabled. if setting.DisableWebhooks { @@ -193,25 +164,19 @@ func PrepareWebhook(ctx context.Context, w *webhook_model.Webhook, event webhook } } - var payloader api.Payloader - var err error - webhook, ok := webhooks[w.Type] - if ok { - payloader, err = webhook.payloadCreator(p, event, w.Meta) - if err != nil { - return fmt.Errorf("create payload for %s[%s]: %w", w.Type, event, err) - } - } else { - payloader = p + payload, err := p.JSONPayload() + if err != nil { + return fmt.Errorf("JSONPayload for %s: %w", event, err) } task, err := webhook_model.CreateHookTask(ctx, &webhook_model.HookTask{ - HookID: w.ID, - Payloader: payloader, - EventType: event, + HookID: w.ID, + PayloadContent: string(payload), + EventType: event, + PayloadVersion: 2, }) if err != nil { - return fmt.Errorf("CreateHookTask: %w", err) + return fmt.Errorf("CreateHookTask for %s: %w", event, err) } return enqueueHookTask(task.ID) diff --git a/services/webhook/webhook_test.go b/services/webhook/webhook_test.go index 338b94360b..5f5c146232 100644 --- a/services/webhook/webhook_test.go +++ b/services/webhook/webhook_test.go @@ -77,7 +77,3 @@ func TestPrepareWebhooksBranchFilterNoMatch(t *testing.T) { unittest.AssertNotExistsBean(t, hookTask) } } - -// TODO TestHookTask_deliver - -// TODO TestDeliverHooks diff --git a/services/webhook/wechatwork.go b/services/webhook/wechatwork.go index 80245c7e77..46e7856ecf 100644 --- a/services/webhook/wechatwork.go +++ b/services/webhook/wechatwork.go @@ -4,11 +4,13 @@ package webhook import ( + "context" "fmt" + "net/http" "strings" + webhook_model "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" ) @@ -28,20 +30,8 @@ type ( } ) -// SetSecret sets the Wechatwork secret -func (f *WechatworkPayload) SetSecret(_ string) {} - -// JSONPayload Marshals the WechatworkPayload to json -func (f *WechatworkPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(f, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -func newWechatworkMarkdownPayload(title string) *WechatworkPayload { - return &WechatworkPayload{ +func newWechatworkMarkdownPayload(title string) WechatworkPayload { + return WechatworkPayload{ Msgtype: "markdown", Markdown: struct { Content string `json:"content"` @@ -51,10 +41,8 @@ func newWechatworkMarkdownPayload(title string) *WechatworkPayload { } } -var _ PayloadConvertor = &WechatworkPayload{} - // Create implements PayloadConvertor Create method -func (f *WechatworkPayload) Create(p *api.CreatePayload) (api.Payloader, error) { +func (wc wechatworkConvertor) Create(p *api.CreatePayload) (WechatworkPayload, error) { // created tag/branch refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) @@ -63,7 +51,7 @@ func (f *WechatworkPayload) Create(p *api.CreatePayload) (api.Payloader, error) } // Delete implements PayloadConvertor Delete method -func (f *WechatworkPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { +func (wc wechatworkConvertor) Delete(p *api.DeletePayload) (WechatworkPayload, error) { // created tag/branch refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) @@ -72,14 +60,14 @@ func (f *WechatworkPayload) Delete(p *api.DeletePayload) (api.Payloader, error) } // Fork implements PayloadConvertor Fork method -func (f *WechatworkPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { +func (wc wechatworkConvertor) Fork(p *api.ForkPayload) (WechatworkPayload, error) { title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) return newWechatworkMarkdownPayload(title), nil } // Push implements PayloadConvertor Push method -func (f *WechatworkPayload) Push(p *api.PushPayload) (api.Payloader, error) { +func (wc wechatworkConvertor) Push(p *api.PushPayload) (WechatworkPayload, error) { var ( branchName = git.RefName(p.Ref).ShortName() commitDesc string @@ -108,7 +96,7 @@ func (f *WechatworkPayload) Push(p *api.PushPayload) (api.Payloader, error) { } // Issue implements PayloadConvertor Issue method -func (f *WechatworkPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { +func (wc wechatworkConvertor) Issue(p *api.IssuePayload) (WechatworkPayload, error) { text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true) var content string content += fmt.Sprintf(" ><font color=\"info\">%s</font>\n >%s \n ><font color=\"warning\"> %s</font> \n [%s](%s)", text, attachmentText, issueTitle, p.Issue.HTMLURL, p.Issue.HTMLURL) @@ -117,7 +105,7 @@ func (f *WechatworkPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { } // IssueComment implements PayloadConvertor IssueComment method -func (f *WechatworkPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { +func (wc wechatworkConvertor) IssueComment(p *api.IssueCommentPayload) (WechatworkPayload, error) { text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true) var content string content += fmt.Sprintf(" ><font color=\"info\">%s</font>\n >%s \n ><font color=\"warning\">%s</font> \n [%s](%s)", text, p.Comment.Body, issueTitle, p.Comment.HTMLURL, p.Comment.HTMLURL) @@ -126,7 +114,7 @@ func (f *WechatworkPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloa } // PullRequest implements PayloadConvertor PullRequest method -func (f *WechatworkPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { +func (wc wechatworkConvertor) PullRequest(p *api.PullRequestPayload) (WechatworkPayload, error) { text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true) pr := fmt.Sprintf("> <font color=\"info\"> %s </font> \r\n > <font color=\"comment\">%s </font> \r\n > <font color=\"comment\">%s </font> \r\n", text, issueTitle, attachmentText) @@ -135,13 +123,13 @@ func (f *WechatworkPayload) PullRequest(p *api.PullRequestPayload) (api.Payloade } // Review implements PayloadConvertor Review method -func (f *WechatworkPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) { +func (wc wechatworkConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (WechatworkPayload, error) { var text, title string switch p.Action { case api.HookIssueReviewed: action, err := parseHookPullRequestEventType(event) if err != nil { - return nil, err + return WechatworkPayload{}, err } title = fmt.Sprintf("[%s] Pull request review %s : #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title) text = p.Review.Content @@ -151,7 +139,7 @@ func (f *WechatworkPayload) Review(p *api.PullRequestPayload, event webhook_modu } // Repository implements PayloadConvertor Repository method -func (f *WechatworkPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { +func (wc wechatworkConvertor) Repository(p *api.RepositoryPayload) (WechatworkPayload, error) { var title string switch p.Action { case api.HookRepoCreated: @@ -162,30 +150,33 @@ func (f *WechatworkPayload) Repository(p *api.RepositoryPayload) (api.Payloader, return newWechatworkMarkdownPayload(title), nil } - return nil, nil + return WechatworkPayload{}, nil } // Wiki implements PayloadConvertor Wiki method -func (f *WechatworkPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) { +func (wc wechatworkConvertor) Wiki(p *api.WikiPayload) (WechatworkPayload, error) { text, _, _ := getWikiPayloadInfo(p, noneLinkFormatter, true) return newWechatworkMarkdownPayload(text), nil } // Release implements PayloadConvertor Release method -func (f *WechatworkPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { +func (wc wechatworkConvertor) Release(p *api.ReleasePayload) (WechatworkPayload, error) { text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true) return newWechatworkMarkdownPayload(text), nil } -func (f *WechatworkPayload) Package(p *api.PackagePayload) (api.Payloader, error) { +func (wc wechatworkConvertor) Package(p *api.PackagePayload) (WechatworkPayload, error) { text, _ := getPackagePayloadInfo(p, noneLinkFormatter, true) return newWechatworkMarkdownPayload(text), nil } -// GetWechatworkPayload GetWechatworkPayload converts a ding talk webhook into a WechatworkPayload -func GetWechatworkPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) { - return convertPayloader(new(WechatworkPayload), p, event) +type wechatworkConvertor struct{} + +var _ payloadConvertor[WechatworkPayload] = wechatworkConvertor{} + +func newWechatworkRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { + return newJSONRequest(wechatworkConvertor{}, w, t, true) } diff --git a/templates/repo/settings/webhook/history.tmpl b/templates/repo/settings/webhook/history.tmpl index 3c21a42421..4e0f0e9c3e 100644 --- a/templates/repo/settings/webhook/history.tmpl +++ b/templates/repo/settings/webhook/history.tmpl @@ -19,6 +19,8 @@ <div class="flex-text-inline"> {{if .IsSucceed}} <span class="text green">{{svg "octicon-check"}}</span> + {{else if not .IsDelivered}} + <span class="text orange">{{svg "octicon-stopwatch"}}</span> {{else}} <span class="text red">{{svg "octicon-alert"}}</span> {{end}} @@ -62,7 +64,7 @@ {{range $key, $val := .RequestInfo.Headers}}<strong>{{$key}}:</strong> {{$val}} {{end}}</pre> <h5>{{ctx.Locale.Tr "repo.settings.webhook.payload"}}</h5> - <pre class="webhook-info"><code class="json">{{.PayloadContent}}</code></pre> + <pre class="webhook-info"><code class="json">{{or .RequestInfo.Body .PayloadContent}}</code></pre> {{else}} - {{end}} From abb029499605717cb077f2a33f9f857a9697bcbf Mon Sep 17 00:00:00 2001 From: wxiaoguang <wxiaoguang@gmail.com> Date: Fri, 8 Mar 2024 11:19:35 +0800 Subject: [PATCH 35/62] Partially enable MSSQL case-sensitive collation support (#29238) Follow #28662 (cherry picked from commit 29a8c8de779924694fadad80b31cc855dd62c0f2) --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- models/db/collation.go | 3 +-- models/project/board.go | 2 +- tests/integration/db_collation_test.go | 6 ------ 5 files changed, 17 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index a2bbb1dfd9..32fdca26fe 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-sql-driver/mysql v1.8.0 github.com/go-swagger/go-swagger v0.30.5 - github.com/go-testfixtures/testfixtures/v3 v3.9.0 + github.com/go-testfixtures/testfixtures/v3 v3.10.0 github.com/go-webauthn/webauthn v0.10.0 github.com/gobwas/glob v0.2.3 github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f @@ -55,7 +55,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.0 github.com/google/go-github/v57 v57.0.0 github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 - github.com/google/uuid v1.5.0 + github.com/google/uuid v1.6.0 github.com/gorilla/feeds v1.1.2 github.com/gorilla/sessions v1.2.2 github.com/hashicorp/go-version v1.6.0 @@ -71,7 +71,7 @@ require ( github.com/lib/pq v1.10.9 github.com/markbates/goth v1.78.0 github.com/mattn/go-isatty v0.0.20 - github.com/mattn/go-sqlite3 v1.14.19 + github.com/mattn/go-sqlite3 v1.14.22 github.com/meilisearch/meilisearch-go v0.26.1 github.com/mholt/archiver/v3 v3.5.1 github.com/microcosm-cc/bluemonday v1.0.26 @@ -126,7 +126,7 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect github.com/ClickHouse/ch-go v0.61.1 // indirect - github.com/ClickHouse/clickhouse-go/v2 v2.17.1 // indirect + github.com/ClickHouse/clickhouse-go/v2 v2.18.0 // indirect github.com/DataDog/zstd v1.5.5 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect @@ -239,7 +239,7 @@ require ( github.com/oklog/ulid v1.3.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect - github.com/paulmach/orb v0.11.0 // indirect + github.com/paulmach/orb v0.11.1 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect diff --git a/go.sum b/go.sum index ceb999d230..6788280b12 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ClickHouse/ch-go v0.61.1 h1:j5rx3qnvcnYjhnP1IdXE/vdIRQiqgwAzyqOaasA6QCw= github.com/ClickHouse/ch-go v0.61.1/go.mod h1:myxt/JZgy2BYHFGQqzmaIpbfr5CMbs3YHVULaWQj5YU= -github.com/ClickHouse/clickhouse-go/v2 v2.17.1 h1:ZCmAYWpu75IyEi7+Yrs/uaAjiCGY5wfW5kXo64exkX4= -github.com/ClickHouse/clickhouse-go/v2 v2.17.1/go.mod h1:rkGTvFDTLqLIm0ma+13xmcCfr/08Gvs7KmFt1tgiWHQ= +github.com/ClickHouse/clickhouse-go/v2 v2.18.0 h1:O1LicIeg2JS2V29fKRH4+yT3f6jvvcJBm506dpVQ4mQ= +github.com/ClickHouse/clickhouse-go/v2 v2.18.0/go.mod h1:ztQvX6wm7kAbhJslS87EXEhOVNY/TObXwyURnGju5FQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -353,8 +353,8 @@ github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.m github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-testfixtures/testfixtures/v3 v3.9.0 h1:938g5V+GWLVejm3Hc+nWCuEXRlcglZDDlN/t1gWzcSY= -github.com/go-testfixtures/testfixtures/v3 v3.9.0/go.mod h1:cdsKD2ApFBjdog9jRsz6EJqF+LClq/hrwE9K/1Dzo4s= +github.com/go-testfixtures/testfixtures/v3 v3.10.0 h1:BrBwN7AuC+74g5qtk9D59TLGOaEa8Bw1WmIsf+SyzWc= +github.com/go-testfixtures/testfixtures/v3 v3.10.0/go.mod h1:z8RoleoNtibi6Ar8ziCW7e6PQ+jWiqbUWvuv8AMe4lo= github.com/go-webauthn/webauthn v0.10.0 h1:yuW2e1tXnRAwAvKrR4q4LQmc6XtCMH639/ypZGhZCwk= github.com/go-webauthn/webauthn v0.10.0/go.mod h1:l0NiauXhL6usIKqNLCUM3Qir43GK7ORg8ggold0Uv/Y= github.com/go-webauthn/x v0.1.6 h1:QNAX+AWeqRt9loE8mULeWJCqhVG5D/jvdmJ47fIWCkQ= @@ -457,8 +457,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -609,8 +609,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= -github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/meilisearch/meilisearch-go v0.26.1 h1:3bmo2uLijX7kvBmiZ9LupVfC95TFcRJDgrRTzbOoE4A= github.com/meilisearch/meilisearch-go v0.26.1/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= @@ -681,8 +681,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU= github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/paulmach/orb v0.11.0 h1:JfVXJUBeH9ifc/OrhBY0lL16QsmPgpCHMlqSSYhcgAA= -github.com/paulmach/orb v0.11.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= +github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= +github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= diff --git a/models/db/collation.go b/models/db/collation.go index 2f5ff2bf05..c128cf5029 100644 --- a/models/db/collation.go +++ b/models/db/collation.go @@ -166,8 +166,7 @@ func preprocessDatabaseCollation(x *xorm.Engine) { // try to alter database collation to expected if the database is empty, it might fail in some cases (and it isn't necessary to succeed) // at the moment, there is no "altering" solution for MSSQL, site admin should manually change the database collation - // and there is a bug https://github.com/go-testfixtures/testfixtures/pull/182 mssql: Invalid object name 'information_schema.tables'. - if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) && r.ExistingTableNumber == 0 && x.Dialect().URI().DBType == schemas.MYSQL { + if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) && r.ExistingTableNumber == 0 { if err = alterDatabaseCollation(x, r.ExpectedCollation); err != nil { log.Error("Failed to change database collation to %q: %v", r.ExpectedCollation, err) } else { diff --git a/models/project/board.go b/models/project/board.go index 3e2d8e0472..c0e6529880 100644 --- a/models/project/board.go +++ b/models/project/board.go @@ -232,7 +232,7 @@ func UpdateBoard(ctx context.Context, board *Board) error { func (p *Project) GetBoards(ctx context.Context) (BoardList, error) { boards := make([]*Board, 0, 5) - if err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, false).OrderBy("Sorting").Find(&boards); err != nil { + if err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, false).OrderBy("sorting").Find(&boards); err != nil { return nil, err } diff --git a/tests/integration/db_collation_test.go b/tests/integration/db_collation_test.go index ab93c105cf..eee26d1ed1 100644 --- a/tests/integration/db_collation_test.go +++ b/tests/integration/db_collation_test.go @@ -49,12 +49,6 @@ func TestDatabaseCollationSelfCheckUI(t *testing.T) { func TestDatabaseCollation(t *testing.T) { x := db.GetEngine(db.DefaultContext).(*xorm.Engine) - // there are blockers for MSSQL to use case-sensitive collation, see the comments in db/collation.go - if setting.Database.Type.IsMSSQL() { - t.Skip("there are blockers for MSSQL to use case-sensitive collation") - return - } - // all created tables should use case-sensitive collation by default _, _ = x.Exec("DROP TABLE IF EXISTS test_collation_tbl") err := x.Sync(&TestCollationTbl{}) From 76db6d9e7411cb69ed991088e3c332c34570bc0b Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Fri, 8 Mar 2024 12:28:21 +0800 Subject: [PATCH 36/62] Fix 500 when deleting account with incorrect password or unsupported login type (#29579) Fix #26210 --------- Co-authored-by: Jason Song <i@wolfogre.com> (cherry picked from commit ce8a98f8789a7e4e9ee97ab0abac6064d78fb1f6) --- options/locale/locale_en-US.ini | 2 ++ routers/web/user/setting/account.go | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index da9fe91075..a7f4de48a8 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -595,6 +595,8 @@ enterred_invalid_repo_name = The repository name you entered is incorrect. enterred_invalid_org_name = The organization name you entered is incorrect. enterred_invalid_owner_name = The new owner name is not valid. enterred_invalid_password = The password you entered is incorrect. +unset_password = The login user has not set the password. +unsupported_login_type = The login type is not supported to delete account. user_not_exist = The user does not exist. team_not_exist = The team does not exist. last_org_owner = You cannot remove the last user from the "owners" team. There must be at least one owner for an organization. diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index ef02cd24a7..386309aa71 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -19,6 +19,8 @@ import ( "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/auth" + "code.gitea.io/gitea/services/auth/source/db" + "code.gitea.io/gitea/services/auth/source/smtp" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/mailer" @@ -251,11 +253,24 @@ func DeleteAccount(ctx *context.Context) { ctx.Data["PageIsSettingsAccount"] = true if _, _, err := auth.UserSignIn(ctx, ctx.Doer.Name, ctx.FormString("password")); err != nil { - if user_model.IsErrUserNotExist(err) { + switch { + case user_model.IsErrUserNotExist(err): + loadAccountData(ctx) + + ctx.RenderWithErr(ctx.Tr("form.user_not_exist"), tplSettingsAccount, nil) + case errors.Is(err, smtp.ErrUnsupportedLoginType): + loadAccountData(ctx) + + ctx.RenderWithErr(ctx.Tr("form.unsupported_login_type"), tplSettingsAccount, nil) + case errors.As(err, &db.ErrUserPasswordNotSet{}): + loadAccountData(ctx) + + ctx.RenderWithErr(ctx.Tr("form.unset_password"), tplSettingsAccount, nil) + case errors.As(err, &db.ErrUserPasswordInvalid{}): loadAccountData(ctx) ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_password"), tplSettingsAccount, nil) - } else { + default: ctx.ServerError("UserSignIn", err) } return From ac2e882dbf62f607f234c65198ce0342f6d8772a Mon Sep 17 00:00:00 2001 From: charles <30816317+charles7668@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:02:13 +0800 Subject: [PATCH 37/62] Fixing the issue when status check per rule matches multiple actions (#29631) Close #29628 rule ``` Test / Build* Test / Build * Test / Build 2* Test / Build 1* ```  rule2 ``` Test / Build* Test / Build 1* ```  rule3 ``` Test / Build* Test / Build 1* NotExist* ```  --------- Co-authored-by: Zettat123 <zettat123@gmail.com> (cherry picked from commit 7cf7a499be80931ae34588201e9605085898e9b8) --- services/pull/commit_status.go | 18 ++++---- services/pull/commit_status_test.go | 65 +++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 services/pull/commit_status_test.go diff --git a/services/pull/commit_status.go b/services/pull/commit_status.go index 27ee572640..fdae8424a3 100644 --- a/services/pull/commit_status.go +++ b/services/pull/commit_status.go @@ -36,9 +36,9 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus, } } - for _, commitStatus := range commitStatuses { + for _, gp := range requiredContextsGlob { var targetStatus structs.CommitStatusState - for _, gp := range requiredContextsGlob { + for _, commitStatus := range commitStatuses { if gp.Match(commitStatus.Context) { targetStatus = commitStatus.State matchedCount++ @@ -46,16 +46,20 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus, } } - if targetStatus != "" && targetStatus.NoBetterThan(returnedStatus) { + // If required rule not match any action, then it is pending + if targetStatus == "" { + if structs.CommitStatusPending.NoBetterThan(returnedStatus) { + returnedStatus = structs.CommitStatusPending + } + break + } + + if targetStatus.NoBetterThan(returnedStatus) { returnedStatus = targetStatus } } } - if matchedCount != len(requiredContexts) { - return structs.CommitStatusPending - } - if matchedCount == 0 { status := git_model.CalcCommitStatus(commitStatuses) if status != nil { diff --git a/services/pull/commit_status_test.go b/services/pull/commit_status_test.go new file mode 100644 index 0000000000..592acdd55c --- /dev/null +++ b/services/pull/commit_status_test.go @@ -0,0 +1,65 @@ +// Copyright 2024 The Gitea Authors. +// All rights reserved. +// SPDX-License-Identifier: MIT + +package pull + +import ( + "testing" + + git_model "code.gitea.io/gitea/models/git" + "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestMergeRequiredContextsCommitStatus(t *testing.T) { + testCases := [][]*git_model.CommitStatus{ + { + {Context: "Build 1", State: structs.CommitStatusSuccess}, + {Context: "Build 2", State: structs.CommitStatusSuccess}, + {Context: "Build 3", State: structs.CommitStatusSuccess}, + }, + { + {Context: "Build 1", State: structs.CommitStatusSuccess}, + {Context: "Build 2", State: structs.CommitStatusSuccess}, + {Context: "Build 2t", State: structs.CommitStatusPending}, + }, + { + {Context: "Build 1", State: structs.CommitStatusSuccess}, + {Context: "Build 2", State: structs.CommitStatusSuccess}, + {Context: "Build 2t", State: structs.CommitStatusFailure}, + }, + { + {Context: "Build 1", State: structs.CommitStatusSuccess}, + {Context: "Build 2", State: structs.CommitStatusSuccess}, + {Context: "Build 2t", State: structs.CommitStatusSuccess}, + }, + { + {Context: "Build 1", State: structs.CommitStatusSuccess}, + {Context: "Build 2", State: structs.CommitStatusSuccess}, + {Context: "Build 2t", State: structs.CommitStatusSuccess}, + }, + } + testCasesRequiredContexts := [][]string{ + {"Build*"}, + {"Build*", "Build 2t*"}, + {"Build*", "Build 2t*"}, + {"Build*", "Build 2t*", "Build 3*"}, + {"Build*", "Build *", "Build 2t*", "Build 1*"}, + } + + testCasesExpected := []structs.CommitStatusState{ + structs.CommitStatusSuccess, + structs.CommitStatusPending, + structs.CommitStatusFailure, + structs.CommitStatusPending, + structs.CommitStatusSuccess, + } + + for i, commitStatuses := range testCases { + if MergeRequiredContextsCommitStatus(commitStatuses, testCasesRequiredContexts[i]) != testCasesExpected[i] { + assert.Fail(t, "Test case failed", "Test case %d failed", i+1) + } + } +} From 1090734255d70deb9886de2c1a8bb971096223ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Niclas=20Oelschl=C3=A4ger?= <72873130+zokkis@users.noreply.github.com> Date: Fri, 8 Mar 2024 06:36:27 +0100 Subject: [PATCH 38/62] Filter for default-branch selection (#29388) Filter for default-branch selection (fixes #4751) before:  after:  --------- Co-authored-by: silverwind <me@silverwind.io> (cherry picked from commit c8f4897f7f5de5b391be806f4738de1f0d9c4c09) --- templates/repo/settings/branches.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/settings/branches.tmpl b/templates/repo/settings/branches.tmpl index 78421ec009..73aff887f3 100644 --- a/templates/repo/settings/branches.tmpl +++ b/templates/repo/settings/branches.tmpl @@ -16,7 +16,7 @@ {{.CsrfTokenHtml}} <input type="hidden" name="action" value="default_branch"> {{if not .Repository.IsEmpty}} - <div class="ui dropdown selection gt-f1 gt-mr-3 tw-max-w-96"> + <div class="ui dropdown selection search gt-f1 gt-mr-3 tw-max-w-96"> {{svg "octicon-triangle-down" 14 "dropdown icon"}} <input type="hidden" name="branch" value="{{.Repository.DefaultBranch}}"> <div class="default text">{{.Repository.DefaultBranch}}</div> From 6cb9e8d86901cf475cf41f601efce9baa5df4155 Mon Sep 17 00:00:00 2001 From: sillyguodong <33891828+sillyguodong@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:14:35 +0800 Subject: [PATCH 39/62] Make runs-on support variable expression (#29468) As title. Close issue: https://gitea.com/gitea/act_runner/issues/445 Follow: https://gitea.com/gitea/act/pulls/91 Move `getSecretsOfTask` and `getVariablesOfTask` under `models` because of circular dependency issues. (cherry picked from commit a1f5dd767729e30d07ab42fda80c19f30a72679f) --- go.mod | 2 +- go.sum | 4 +- models/actions/variable.go | 33 ++++++++++++ models/secret/secret.go | 39 ++++++++++++++ routers/api/actions/runner/utils.go | 80 +++++------------------------ services/actions/notifier_helper.go | 13 ++++- 6 files changed, 99 insertions(+), 72 deletions(-) diff --git a/go.mod b/go.mod index 32fdca26fe..837b233171 100644 --- a/go.mod +++ b/go.mod @@ -299,7 +299,7 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 -replace github.com/nektos/act => gitea.com/gitea/act v0.2.51 +replace github.com/nektos/act => gitea.com/gitea/act v0.259.1 replace github.com/gorilla/feeds => github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5 diff --git a/go.sum b/go.sum index 6788280b12..5f7c04bb0e 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= -gitea.com/gitea/act v0.2.51 h1:gXc/B4OlTciTTzAx9cmNyw04n2SDO7exPjAsR5Idu+c= -gitea.com/gitea/act v0.2.51/go.mod h1:CoaX2053jqBlD6JMgu4d4UgFL/rp2I14Kt5mMqcs0Z0= +gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw= +gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8= gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669 h1:RUBX+MK/TsDxpHmymaOaydfigEbbzqUnG1OTZU/HAeo= gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc= gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0= diff --git a/models/actions/variable.go b/models/actions/variable.go index 12717e0ae4..14ded60fac 100644 --- a/models/actions/variable.go +++ b/models/actions/variable.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -82,3 +83,35 @@ func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) }) return count != 0, err } + +func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) { + variables := map[string]string{} + + // Global + globalVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{}) + if err != nil { + log.Error("find global variables: %v", err) + return nil, err + } + + // Org / User level + ownerVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{OwnerID: run.Repo.OwnerID}) + if err != nil { + log.Error("find variables of org: %d, error: %v", run.Repo.OwnerID, err) + return nil, err + } + + // Repo level + repoVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{RepoID: run.RepoID}) + if err != nil { + log.Error("find variables of repo: %d, error: %v", run.RepoID, err) + return nil, err + } + + // Level precedence: Repo > Org / User > Global + for _, v := range append(globalVariables, append(ownerVariables, repoVariables...)...) { + variables[v.Name] = v.Data + } + + return variables, nil +} diff --git a/models/secret/secret.go b/models/secret/secret.go index 41e860d7f6..35bed500b9 100644 --- a/models/secret/secret.go +++ b/models/secret/secret.go @@ -9,7 +9,10 @@ import ( "fmt" "strings" + actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" + actions_module "code.gitea.io/gitea/modules/actions" + "code.gitea.io/gitea/modules/log" secret_module "code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -112,3 +115,39 @@ func UpdateSecret(ctx context.Context, secretID int64, data string) error { } return err } + +func GetSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) (map[string]string, error) { + secrets := map[string]string{} + + secrets["GITHUB_TOKEN"] = task.Token + secrets["GITEA_TOKEN"] = task.Token + + if task.Job.Run.IsForkPullRequest && task.Job.Run.TriggerEvent != actions_module.GithubEventPullRequestTarget { + // ignore secrets for fork pull request, except GITHUB_TOKEN and GITEA_TOKEN which are automatically generated. + // for the tasks triggered by pull_request_target event, they could access the secrets because they will run in the context of the base branch + // see the documentation: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target + return secrets, nil + } + + ownerSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{OwnerID: task.Job.Run.Repo.OwnerID}) + if err != nil { + log.Error("find secrets of owner %v: %v", task.Job.Run.Repo.OwnerID, err) + return nil, err + } + repoSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{RepoID: task.Job.Run.RepoID}) + if err != nil { + log.Error("find secrets of repo %v: %v", task.Job.Run.RepoID, err) + return nil, err + } + + for _, secret := range append(ownerSecrets, repoSecrets...) { + v, err := secret_module.DecryptSecret(setting.SecretKey, secret.Data) + if err != nil { + log.Error("decrypt secret %v %q: %v", secret.ID, secret.Name, err) + return nil, err + } + secrets[secret.Name] = v + } + + return secrets, nil +} diff --git a/routers/api/actions/runner/utils.go b/routers/api/actions/runner/utils.go index a7cb31288c..ff6ec5bd54 100644 --- a/routers/api/actions/runner/utils.go +++ b/routers/api/actions/runner/utils.go @@ -15,7 +15,6 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" - secret_module "code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/actions" @@ -32,14 +31,24 @@ func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv return nil, false, nil } + secrets, err := secret_model.GetSecretsOfTask(ctx, t) + if err != nil { + return nil, false, fmt.Errorf("GetSecretsOfTask: %w", err) + } + + vars, err := actions_model.GetVariablesOfRun(ctx, t.Job.Run) + if err != nil { + return nil, false, fmt.Errorf("GetVariablesOfRun: %w", err) + } + actions.CreateCommitStatus(ctx, t.Job) task := &runnerv1.Task{ Id: t.ID, WorkflowPayload: t.Job.WorkflowPayload, Context: generateTaskContext(t), - Secrets: getSecretsOfTask(ctx, t), - Vars: getVariablesOfTask(ctx, t), + Secrets: secrets, + Vars: vars, } if needs, err := findTaskNeeds(ctx, t); err != nil { @@ -55,71 +64,6 @@ func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv return task, true, nil } -func getSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) map[string]string { - secrets := map[string]string{} - - secrets["GITHUB_TOKEN"] = task.Token - secrets["GITEA_TOKEN"] = task.Token - - if task.Job.Run.IsForkPullRequest && task.Job.Run.TriggerEvent != actions_module.GithubEventPullRequestTarget { - // ignore secrets for fork pull request, except GITHUB_TOKEN and GITEA_TOKEN which are automatically generated. - // for the tasks triggered by pull_request_target event, they could access the secrets because they will run in the context of the base branch - // see the documentation: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target - return secrets - } - - ownerSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: task.Job.Run.Repo.OwnerID}) - if err != nil { - log.Error("find secrets of owner %v: %v", task.Job.Run.Repo.OwnerID, err) - // go on - } - repoSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{RepoID: task.Job.Run.RepoID}) - if err != nil { - log.Error("find secrets of repo %v: %v", task.Job.Run.RepoID, err) - // go on - } - - for _, secret := range append(ownerSecrets, repoSecrets...) { - if v, err := secret_module.DecryptSecret(setting.SecretKey, secret.Data); err != nil { - log.Error("decrypt secret %v %q: %v", secret.ID, secret.Name, err) - // go on - } else { - secrets[secret.Name] = v - } - } - - return secrets -} - -func getVariablesOfTask(ctx context.Context, task *actions_model.ActionTask) map[string]string { - variables := map[string]string{} - - // Global - globalVariables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{}) - if err != nil { - log.Error("find global variables: %v", err) - } - - // Org / User level - ownerVariables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{OwnerID: task.Job.Run.Repo.OwnerID}) - if err != nil { - log.Error("find variables of org: %d, error: %v", task.Job.Run.Repo.OwnerID, err) - } - - // Repo level - repoVariables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{RepoID: task.Job.Run.RepoID}) - if err != nil { - log.Error("find variables of repo: %d, error: %v", task.Job.Run.RepoID, err) - } - - // Level precedence: Repo > Org / User > Global - for _, v := range append(globalVariables, append(ownerVariables, repoVariables...)...) { - variables[v.Name] = v.Data - } - - return variables -} - func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct { event := map[string]any{} _ = json.Unmarshal([]byte(t.Job.Run.EventPayload), &event) diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index 19a5c66b71..6f0e8058a0 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -305,7 +305,18 @@ func handleWorkflows( run.NeedApproval = need } - jobs, err := jobparser.Parse(dwf.Content) + if err := run.LoadAttributes(ctx); err != nil { + log.Error("LoadAttributes: %v", err) + continue + } + + vars, err := actions_model.GetVariablesOfRun(ctx, run) + if err != nil { + log.Error("GetVariablesOfRun: %v", err) + continue + } + + jobs, err := jobparser.Parse(dwf.Content, jobparser.WithVars(vars)) if err != nil { log.Error("jobparser.Parse: %v", err) continue From 3d9afe8813504c2e5dc599c0379d23fadf321310 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Fri, 8 Mar 2024 15:30:10 +0800 Subject: [PATCH 40/62] Move get/set default branch from git package to gitrepo package to hide repopath (#29126) (cherry picked from commit 25b842df261452a29570ba89ffc3a4842d73f68c) Conflicts: routers/web/repo/wiki.go services/repository/branch.go services/repository/migrate.go services/wiki/wiki.go also apply to Forgejo specific usage of the refactored functions --- models/issues/pull.go | 7 +------ modules/git/repo_branch.go | 11 ++--------- modules/gitrepo/branch.go | 17 +++++++++++++++++ routers/api/v1/repo/repo.go | 4 ++-- routers/private/default_branch.go | 3 ++- routers/web/repo/setting/default_branch.go | 3 ++- routers/web/repo/view.go | 3 ++- services/context/repo.go | 2 +- services/mirror/mirror_pull.go | 2 +- services/repository/adopt.go | 22 +++++++++++----------- services/repository/branch.go | 2 +- services/repository/create.go | 7 +------ services/repository/generate.go | 7 +------ services/repository/migrate.go | 3 ++- services/repository/push.go | 2 +- services/wiki/wiki.go | 3 ++- 16 files changed, 49 insertions(+), 49 deletions(-) diff --git a/models/issues/pull.go b/models/issues/pull.go index 84a62ddc90..a2c116c4c0 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -909,12 +909,7 @@ func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullReque } defer repo.Close() - branch, err := repo.GetDefaultBranch() - if err != nil { - return err - } - - commit, err := repo.GetBranchCommit(branch) + commit, err := repo.GetBranchCommit(pr.BaseRepo.DefaultBranch) if err != nil { return err } diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index 979c5dec91..552ae2bb8c 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -55,15 +55,8 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) { }, nil } -// SetDefaultBranch sets default branch of repository. -func (repo *Repository) SetDefaultBranch(name string) error { - _, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").AddDynamicArguments(BranchPrefix + name).RunStdString(&RunOpts{Dir: repo.Path}) - return err -} - -// GetDefaultBranch gets default branch of repository. -func (repo *Repository) GetDefaultBranch() (string, error) { - stdout, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repo.Path}) +func GetDefaultBranch(ctx context.Context, repoPath string) (string, error) { + stdout, _, err := NewCommand(ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return "", err } diff --git a/modules/gitrepo/branch.go b/modules/gitrepo/branch.go index dcaf92668d..e13a4c82e1 100644 --- a/modules/gitrepo/branch.go +++ b/modules/gitrepo/branch.go @@ -30,3 +30,20 @@ func GetBranchCommitID(ctx context.Context, repo Repository, branch string) (str return gitRepo.GetBranchCommitID(branch) } + +// SetDefaultBranch sets default branch of repository. +func SetDefaultBranch(ctx context.Context, repo Repository, name string) error { + _, _, err := git.NewCommand(ctx, "symbolic-ref", "HEAD"). + AddDynamicArguments(git.BranchPrefix + name). + RunStdString(&git.RunOpts{Dir: repoPath(repo)}) + return err +} + +// GetDefaultBranch gets default branch of repository. +func GetDefaultBranch(ctx context.Context, repo Repository) (string, error) { + return git.GetDefaultBranch(ctx, repoPath(repo)) +} + +func GetWikiDefaultBranch(ctx context.Context, repo Repository) (string, error) { + return git.GetDefaultBranch(ctx, wikiPath(repo)) +} diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 40960144c7..316a1161da 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -720,7 +720,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err if ctx.Repo.GitRepo == nil && !repo.IsEmpty { var err error - ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository) + ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, repo) if err != nil { ctx.Error(http.StatusInternalServerError, "Unable to OpenRepository", err) return err @@ -731,7 +731,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err // Default branch only updated if changed and exist or the repository is empty if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && (repo.IsEmpty || ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch)) { if !repo.IsEmpty { - if err := ctx.Repo.GitRepo.SetDefaultBranch(*opts.DefaultBranch); err != nil { + if err := gitrepo.SetDefaultBranch(ctx, ctx.Repo.Repository, *opts.DefaultBranch); err != nil { if !git.IsErrUnsupportedVersion(err) { ctx.Error(http.StatusInternalServerError, "SetDefaultBranch", err) return err diff --git a/routers/private/default_branch.go b/routers/private/default_branch.go index 2e323129ef..33890be6a9 100644 --- a/routers/private/default_branch.go +++ b/routers/private/default_branch.go @@ -9,6 +9,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/private" gitea_context "code.gitea.io/gitea/services/context" ) @@ -20,7 +21,7 @@ func SetDefaultBranch(ctx *gitea_context.PrivateContext) { branch := ctx.Params(":branch") ctx.Repo.Repository.DefaultBranch = branch - if err := ctx.Repo.GitRepo.SetDefaultBranch(ctx.Repo.Repository.DefaultBranch); err != nil { + if err := gitrepo.SetDefaultBranch(ctx, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch); err != nil { if !git.IsErrUnsupportedVersion(err) { ctx.JSON(http.StatusInternalServerError, private.Response{ Err: fmt.Sprintf("Unable to set default branch on repository: %s/%s Error: %v", ownerName, repoName, err), diff --git a/routers/web/repo/setting/default_branch.go b/routers/web/repo/setting/default_branch.go index 610fc5bcdf..d0b32ef079 100644 --- a/routers/web/repo/setting/default_branch.go +++ b/routers/web/repo/setting/default_branch.go @@ -8,6 +8,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers/web/repo" @@ -40,7 +41,7 @@ func SetDefaultBranchPost(ctx *context.Context) { return } else if repo.DefaultBranch != branch { repo.DefaultBranch = branch - if err := ctx.Repo.GitRepo.SetDefaultBranch(branch); err != nil { + if err := gitrepo.SetDefaultBranch(ctx, repo, branch); err != nil { if !git.IsErrUnsupportedVersion(err) { ctx.ServerError("SetDefaultBranch", err) return diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 2670499d4f..842d14232e 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -36,6 +36,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -1073,7 +1074,7 @@ func renderCode(ctx *context.Context) { if err != nil { continue } - defaultBranch, err := gitRepo.GetDefaultBranch() + defaultBranch, err := gitrepo.GetDefaultBranch(ctx, repo) if err != nil { continue } diff --git a/services/context/repo.go b/services/context/repo.go index e78dfc585d..43eeab8098 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -702,7 +702,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc { if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) { ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch } else { - ctx.Repo.BranchName, _ = gitRepo.GetDefaultBranch() + ctx.Repo.BranchName, _ = gitrepo.GetDefaultBranch(ctx, ctx.Repo.Repository) if ctx.Repo.BranchName == "" { // If it still can't get a default branch, fall back to default branch from setting. // Something might be wrong. Either site admin should fix the repo sync or Gitea should fix a potential bug. diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 3418cf90df..de4a58f27b 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -593,7 +593,7 @@ func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, gi m.Repo.DefaultBranch = firstName } // Update the git repository default branch - if err := gitRepo.SetDefaultBranch(m.Repo.DefaultBranch); err != nil { + if err := gitrepo.SetDefaultBranch(ctx, m.Repo, m.Repo.DefaultBranch); err != nil { if !git.IsErrUnsupportedVersion(err) { log.Error("Failed to update default branch of underlying git repository %-v. Error: %v", m.Repo, err) desc := fmt.Sprintf("Failed to update default branch of underlying git repository '%s': %v", m.Repo.RepoPath(), err) diff --git a/services/repository/adopt.go b/services/repository/adopt.go index 7ca68776b5..0ac3c774b7 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -127,24 +127,17 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r repo.IsEmpty = false - // Don't bother looking this repo in the context it won't be there - gitRepo, err := gitrepo.OpenRepository(ctx, repo) - if err != nil { - return fmt.Errorf("openRepository: %w", err) - } - defer gitRepo.Close() - if len(defaultBranch) > 0 { repo.DefaultBranch = defaultBranch - if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { + if err = gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil { return fmt.Errorf("setDefaultBranch: %w", err) } } else { - repo.DefaultBranch, err = gitRepo.GetDefaultBranch() + repo.DefaultBranch, err = gitrepo.GetDefaultBranch(ctx, repo) if err != nil { repo.DefaultBranch = setting.Repository.DefaultBranch - if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { + if err = gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil { return fmt.Errorf("setDefaultBranch: %w", err) } } @@ -188,7 +181,7 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r repo.DefaultBranch = setting.Repository.DefaultBranch } - if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { + if err = gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil { return fmt.Errorf("setDefaultBranch: %w", err) } } @@ -197,6 +190,13 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r return fmt.Errorf("updateRepository: %w", err) } + // Don't bother looking this repo in the context it won't be there + gitRepo, err := gitrepo.OpenRepository(ctx, repo) + if err != nil { + return fmt.Errorf("openRepository: %w", err) + } + defer gitRepo.Close() + if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { return fmt.Errorf("SyncReleasesWithTags: %w", err) } diff --git a/services/repository/branch.go b/services/repository/branch.go index c3a7282f43..d349db7f74 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -317,7 +317,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m } if isDefault { - err2 = gitRepo.SetDefaultBranch(to) + err2 = gitrepo.SetDefaultBranch(ctx, repo, to) if err2 != nil { return err2 } diff --git a/services/repository/create.go b/services/repository/create.go index 9bc0b93eff..d092d02a1f 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -177,12 +177,7 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re if len(opts.DefaultBranch) > 0 { repo.DefaultBranch = opts.DefaultBranch - gitRepo, err := gitrepo.OpenRepository(ctx, repo) - if err != nil { - return fmt.Errorf("openRepository: %w", err) - } - defer gitRepo.Close() - if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { + if err = gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil { return fmt.Errorf("setDefaultBranch: %w", err) } diff --git a/services/repository/generate.go b/services/repository/generate.go index c444b60b2c..9b09e271ab 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -272,12 +272,7 @@ func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *r repo.DefaultBranch = templateRepo.DefaultBranch } - gitRepo, err := gitrepo.OpenRepository(ctx, repo) - if err != nil { - return fmt.Errorf("openRepository: %w", err) - } - defer gitRepo.Close() - if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { + if err = gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil { return fmt.Errorf("setDefaultBranch: %w", err) } if err = UpdateRepository(ctx, repo, false); err != nil { diff --git a/services/repository/migrate.go b/services/repository/migrate.go index b218a2ef46..5800f2b5cb 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -16,6 +16,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/migration" @@ -97,7 +98,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, } defer gitRepo.Close() - branch, err := gitRepo.GetDefaultBranch() + branch, err := gitrepo.GetDefaultBranch(ctx, repo) if err != nil { log.Warn("Failed to get the default branch of a migrated wiki repo: %v", err) if err := util.RemoveAll(wikiPath); err != nil { diff --git a/services/repository/push.go b/services/repository/push.go index bb080e30cc..5d10237845 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -183,7 +183,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { repo.DefaultBranch = refName repo.IsEmpty = false if repo.DefaultBranch != setting.Repository.DefaultBranch { - if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { + if err := gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil { if !git.IsErrUnsupportedVersion(err) { return err } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 81e0b84ea8..01c8cf987f 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/sync" @@ -87,7 +88,7 @@ func NormalizeWikiBranch(ctx context.Context, repo *repo_model.Repository, to st return err } - if err := gitRepo.SetDefaultBranch(to); err != nil { + if err := gitrepo.SetDefaultBranch(ctx, repo, to); err != nil { return err } From 427ab550a6a35e7369bc1b33a188bb3030c32ec0 Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Fri, 8 Mar 2024 08:57:52 +0100 Subject: [PATCH 41/62] Set user's 24h preference from their current OS locale (#29651) Fixes: https://github.com/go-gitea/gitea/issues/28371 Fixed by using a JS solution that formats according to `lang`, but alters the 24h format setting as per user's locale. This will work for all tooltips: <img width="243" alt="Screenshot 2024-03-07 at 23 03 35" src="https://github.com/go-gitea/gitea/assets/115237/6d16c71c-6786-4eda-8cdc-50ec68ba62c6"> <img width="250" alt="Screenshot 2024-03-07 at 23 03 17" src="https://github.com/go-gitea/gitea/assets/115237/4e26bbb7-12df-4b81-bd37-14705e87e8f7"> <img width="310" alt="Screenshot 2024-03-07 at 23 14 34" src="https://github.com/go-gitea/gitea/assets/115237/1ef599f0-6401-4e19-b1da-59cdfc09b0f6"> I think there is only one other place in the UI where we render such absolute dates, which is in the actions view and which I've also fixed: <img width="275" alt="Screenshot 2024-03-07 at 23 04 00" src="https://github.com/go-gitea/gitea/assets/115237/df0fbe1f-96ee-4338-ab5e-2b10e215005d"> (cherry picked from commit f86e9a03673b70d660a4b7a1e53748757d7a45fa) --- web_src/js/components/RepoActionView.vue | 4 ++-- web_src/js/modules/tippy.js | 10 +++++++++- web_src/js/utils/time.js | 21 +++++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index e1e5d1bc13..83a10d3fea 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -3,7 +3,7 @@ import {SvgIcon} from '../svg.js'; import ActionRunStatus from './ActionRunStatus.vue'; import {createApp} from 'vue'; import {toggleElem} from '../utils/dom.js'; -import {getCurrentLocale} from '../utils.js'; +import {formatDatetime} from '../utils/time.js'; import {renderAnsi} from '../render/ansi.js'; import {POST, DELETE} from '../modules/fetch.js'; @@ -170,7 +170,7 @@ const sfc = { const logTimeStamp = document.createElement('span'); logTimeStamp.className = 'log-time-stamp'; const date = new Date(parseFloat(line.timestamp * 1000)); - const timeStamp = date.toLocaleString(getCurrentLocale(), {timeZoneName: 'short'}); + const timeStamp = formatDatetime(date); logTimeStamp.textContent = timeStamp; toggleElem(logTimeStamp, this.timeVisible['log-time-stamp']); // for "Show seconds" diff --git a/web_src/js/modules/tippy.js b/web_src/js/modules/tippy.js index 27f371fd88..489afc0ae1 100644 --- a/web_src/js/modules/tippy.js +++ b/web_src/js/modules/tippy.js @@ -1,5 +1,6 @@ import tippy, {followCursor} from 'tippy.js'; import {isDocumentFragmentOrElementNode} from '../utils/dom.js'; +import {formatDatetime} from '../utils/time.js'; const visibleInstances = new Set(); @@ -93,8 +94,15 @@ function attachTooltip(target, content = null) { } function switchTitleToTooltip(target) { - const title = target.getAttribute('title'); + let title = target.getAttribute('title'); if (title) { + // apply custom formatting to relative-time's tooltips + if (target.tagName.toLowerCase() === 'relative-time') { + const datetime = target.getAttribute('datetime'); + if (datetime) { + title = formatDatetime(new Date(datetime)); + } + } target.setAttribute('data-tooltip-content', title); target.setAttribute('aria-label', title); // keep the attribute, in case there are some other "[title]" selectors diff --git a/web_src/js/utils/time.js b/web_src/js/utils/time.js index 3284e893e1..1848792c98 100644 --- a/web_src/js/utils/time.js +++ b/web_src/js/utils/time.js @@ -1,4 +1,5 @@ import dayjs from 'dayjs'; +import {getCurrentLocale} from '../utils.js'; // Returns an array of millisecond-timestamps of start-of-week days (Sundays) export function startDaysBetween(startDate, endDate) { @@ -44,3 +45,23 @@ export function fillEmptyStartDaysWithZeroes(startDays, data) { return Object.values(result); } + +let dateFormat; + +// format a Date object to document's locale, but with 24h format from user's current locale because this +// option is a personal preference of the user, not something that the document's locale should dictate. +export function formatDatetime(date) { + if (!dateFormat) { + // TODO: replace `hour12` with `Intl.Locale.prototype.getHourCycles` once there is broad browser support + dateFormat = new Intl.DateTimeFormat(getCurrentLocale(), { + day: 'numeric', + month: 'short', + year: 'numeric', + hour: 'numeric', + hour12: !Number.isInteger(Number(new Intl.DateTimeFormat([], {hour: 'numeric'}).format())), + minute: '2-digit', + timeZoneName: 'short', + }); + } + return dateFormat.format(date); +} From fb777840170e97e798be2d4824131b06655e876a Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Fri, 8 Mar 2024 17:44:50 +0900 Subject: [PATCH 42/62] Fix incorrect rendering csv file when file size is larger than UI.CSV.MaxFileSize (#29653) Fix #29506 (cherry picked from commit 9dc8a6336edddca0f3f90392bbc398f4ceaeaf13) --- modules/markup/csv/csv.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/markup/csv/csv.go b/modules/markup/csv/csv.go index 7af34a6cbc..12458e954a 100644 --- a/modules/markup/csv/csv.go +++ b/modules/markup/csv/csv.go @@ -93,8 +93,10 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri if _, err := tmpBlock.WriteString(html.EscapeString(string(rawBytes))); err != nil { return err } - _, err = tmpBlock.WriteString("</pre>") - return err + if _, err := tmpBlock.WriteString("</pre>"); err != nil { + return err + } + return tmpBlock.Flush() } rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, bytes.NewReader(rawBytes)) From faac0a7c3224b2c59d2814390b9d04c377c84bd1 Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Fri, 8 Mar 2024 10:42:12 +0100 Subject: [PATCH 43/62] Style fomantic grey labels (#29458) Fomantic grey labels in the dashboard repo lists were showing original fomantic colors, fixed that. Also slightly tweaked the light theme colors so it uses same opacity values as dark theme. <img width="165" alt="Screenshot 2024-03-07 at 21 06 23" src="https://github.com/go-gitea/gitea/assets/115237/72744d6f-2ee1-4e5d-8ba0-b482a446f535"> <img width="167" alt="Screenshot 2024-03-07 at 21 06 00" src="https://github.com/go-gitea/gitea/assets/115237/1ba93775-e5a9-4b28-b90f-59c1e9199687"> (cherry picked from commit 114bb505a3b0819db683d4b586e950df6a17bff8) --- web_src/css/base.css | 4 +++- web_src/css/themes/theme-gitea-light.css | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/web_src/css/base.css b/web_src/css/base.css index 976f2f9f33..9f402d900d 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -1459,7 +1459,9 @@ strong.attention-caution, span.attention-caution { } .ui.label, -.ui.menu .item > .label { +.ui.menu .item > .label, +.ui.grey.labels .label, +.ui.ui.ui.grey.label { background: var(--color-label-bg); color: var(--color-label-text); } diff --git a/web_src/css/themes/theme-gitea-light.css b/web_src/css/themes/theme-gitea-light.css index 5137e0774c..fbe2458ed6 100644 --- a/web_src/css/themes/theme-gitea-light.css +++ b/web_src/css/themes/theme-gitea-light.css @@ -228,9 +228,9 @@ --color-nav-hover-bg: #ebebeb; --color-nav-text: var(--color-text); --color-label-text: var(--color-text); - --color-label-bg: #cacaca5b; - --color-label-hover-bg: #cacacaa0; - --color-label-active-bg: #cacacaff; + --color-label-bg: #9d9d9d4b; + --color-label-hover-bg: #9d9d9da0; + --color-label-active-bg: #9d9d9dff; --color-accent: var(--color-primary-light-1); --color-small-accent: var(--color-primary-light-6); --color-active-line: #fffbdd; From 4e5381f8ee4bbbff738cf1141130742da2d6b4ae Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Fri, 8 Mar 2024 10:47:32 +0100 Subject: [PATCH 44/62] Don't show AbortErrors on logout (#29639) When logging out of Gitea, a error toast can be seen for a split second. I don't know why or how it happens but I found it it's an `AbortError` (related to [AbortController#abort](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort)), so let's hide it. (cherry picked from commit 886e90aa82521d2c2ae17d3e177c056ae32e4aa6) --- web_src/js/features/common-global.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index 211253ef9a..ee4ade1f04 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -105,8 +105,10 @@ async function fetchActionDoRequest(actionElem, url, opt) { showErrorToast(`server error: ${resp.status}`); } } catch (e) { - console.error('error when doRequest', e); - showErrorToast(`${i18n.network_error} ${e}`); + if (e.name !== 'AbortError') { + console.error('error when doRequest', e); + showErrorToast(`${i18n.network_error} ${e}`); + } } actionElem.classList.remove('is-loading', 'small-loading-icon'); } From 2e49c82f80ecbab40d10442d132b6a0aee4a5c25 Mon Sep 17 00:00:00 2001 From: DC <106393991+DanielMatiasCarvalho@users.noreply.github.com> Date: Fri, 8 Mar 2024 09:53:01 +0000 Subject: [PATCH 45/62] Fix user-defined markup links targets (#29305) This seeks to fix the bug reported on issue #29196. Cause: ID's with custom characters (- , _ , etc.), were not linking correctly in the Markdown file when rendered in the browser because the ID in the respective destinies would be different than the one in anchor, while for IDs with only letters, the ID would be the same. Fix: It was suggested that to fix this bug, it should more or less like GitHub does it. While in gitea the anchors would be put in HTML like this: ``` <p dir="auto"><a href="#user-content-_toc152597800" rel="nofollow">Review</a></p> <p dir="auto"><a href="#user-content-_toc152597802" rel="nofollow">Staging</a></p> <p dir="auto"><a href="#user-content-_toc152597803" rel="nofollow">Development</a></p> <p dir="auto"><a href="#user-content-_toc152597828" rel="nofollow">Testing</a></p> <p dir="auto"><a href="#user-content-_toc152597829" rel="nofollow">Unit-tests</a></p> ``` In GitHub, the same anchor's href properties would be the same without "user-content-" trailing behind. So my code made sure to change those anchors, so it would not include "user-content-" and then add respective Event Listeners so it would scroll into the supposed places. Fixes: #29196 --------- Co-authored-by: silverwind <me@silverwind.io> (cherry picked from commit f219ea8d0e0eccd74c786202a4ddb2784a0175ad) --- web_src/js/markup/anchors.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/web_src/js/markup/anchors.js b/web_src/js/markup/anchors.js index 53dfa2980c..03934ea215 100644 --- a/web_src/js/markup/anchors.js +++ b/web_src/js/markup/anchors.js @@ -2,6 +2,7 @@ import {svg} from '../svg.js'; const headingSelector = '.markup h1, .markup h2, .markup h3, .markup h4, .markup h5, .markup h6'; +// scroll to anchor while respecting the `user-content` prefix that exists on the target function scrollToAnchor(hash, initial) { // abort if the browser has already scrolled to another anchor during page load if (initial && document.querySelector(':target')) return; @@ -19,6 +20,7 @@ function scrollToAnchor(hash, initial) { export function initMarkupAnchors() { if (!document.querySelector('.markup')) return; + // create link icons for markup headings, the resulting link href will remove `user-content-` for (const heading of document.querySelectorAll(headingSelector)) { const originalId = heading.id.replace(/^user-content-/, ''); const a = document.createElement('a'); @@ -31,5 +33,18 @@ export function initMarkupAnchors() { heading.prepend(a); } + // handle user-defined `name` anchors like `[Link](#link)` linking to `<a name="link"></a>Link` + for (const a of document.querySelectorAll('.markup a[href^="#"]')) { + const href = a.getAttribute('href'); + if (!href.startsWith('#user-content-')) continue; + const originalId = href.replace(/^#user-content-/, ''); + a.setAttribute('href', `#${encodeURIComponent(originalId)}`); + if (document.getElementsByName(originalId).length !== 1) { + a.addEventListener('click', (e) => { + scrollToAnchor(e.currentTarget.getAttribute('href'), false); + }); + } + } + scrollToAnchor(window.location.hash, true); } From 18e0647c847afb24819b70edc055fc9371db75ed Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Sat, 9 Mar 2024 00:10:01 +0900 Subject: [PATCH 46/62] bump python version to 3.12 in dev container (#29670)  The default version is 3.9.2, which is not supported by poetry. --------- Co-authored-by: silverwind <me@silverwind.io> (cherry picked from commit b253463e959c44cbd212fe1d662f2520ebfe38e6) --- .devcontainer/devcontainer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9e290fb6a5..8563aafd04 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,7 +8,9 @@ }, "ghcr.io/devcontainers/features/git-lfs:1.1.0": {}, "ghcr.io/devcontainers-contrib/features/poetry:2": {}, - "ghcr.io/devcontainers/features/python:1": {} + "ghcr.io/devcontainers/features/python:1": { + "version": "3.12" + } }, "customizations": { "vscode": { From 45112876766cb81ed7edd2b72a3ab93e6deab8bb Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Fri, 8 Mar 2024 16:15:58 +0100 Subject: [PATCH 47/62] Support pasting URLs over markdown text (#29566) Support pasting URLs over selection text in the textarea editor. Does not work in EasyMDE and I don't intend to support it. Image paste works as usual in both Textarea and EasyMDE. The new `replaceTextareaSelection` function changes textarea content via [`insertText`](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand#using_inserttext) command, which preserves history, e.g. `CTRL-Z` works and is also demostrated below. We should later refactor the image paste code to use the same function because it currently destroys history. Overriding the formatting via `Shift` key is supported as well, e.g. `Ctrl+Shift+V` will insert the URL as-is, like on GitHub.  (cherry picked from commit a3cfe6f39ba33cea305de592a006727857014c53) --- .../js/features/comp/ComboMarkdownEditor.js | 17 +++++- .../features/comp/{ImagePaste.js => Paste.js} | 60 ++++++++++--------- web_src/js/utils/dom.js | 36 +++++++++++ web_src/js/utils/url.js | 12 ++++ web_src/js/utils/url.test.js | 9 ++- 5 files changed, 103 insertions(+), 31 deletions(-) rename web_src/js/features/comp/{ImagePaste.js => Paste.js} (72%) diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js index 4c973358e3..1e7b554b98 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.js +++ b/web_src/js/features/comp/ComboMarkdownEditor.js @@ -3,7 +3,7 @@ import '@github/text-expander-element'; import $ from 'jquery'; import {attachTribute} from '../tribute.js'; import {hideElem, showElem, autosize, isElemVisible} from '../../utils/dom.js'; -import {initEasyMDEImagePaste, initTextareaImagePaste} from './ImagePaste.js'; +import {initEasyMDEPaste, initTextareaPaste} from './Paste.js'; import {handleGlobalEnterQuickSubmit} from './QuickSubmit.js'; import {renderPreviewPanelContent} from '../repo-editor.js'; import {easyMDEToolbarActions} from './EasyMDEToolbarActions.js'; @@ -84,6 +84,17 @@ class ComboMarkdownEditor { if (el.nodeName === 'BUTTON' && !el.getAttribute('type')) el.setAttribute('type', 'button'); } + this.textarea.addEventListener('keydown', (e) => { + if (e.shiftKey) { + e.target._shiftDown = true; + } + }); + this.textarea.addEventListener('keyup', (e) => { + if (!e.shiftKey) { + e.target._shiftDown = false; + } + }); + const monospaceButton = this.container.querySelector('.markdown-switch-monospace'); const monospaceEnabled = localStorage?.getItem('markdown-editor-monospace') === 'true'; const monospaceText = monospaceButton.getAttribute(monospaceEnabled ? 'data-disable-text' : 'data-enable-text'); @@ -108,7 +119,7 @@ class ComboMarkdownEditor { }); if (this.dropzone) { - initTextareaImagePaste(this.textarea, this.dropzone); + initTextareaPaste(this.textarea, this.dropzone); } } @@ -241,7 +252,7 @@ class ComboMarkdownEditor { }); this.applyEditorHeights(this.container.querySelector('.CodeMirror-scroll'), this.options.editorHeights); await attachTribute(this.easyMDE.codemirror.getInputField(), {mentions: true, emoji: true}); - initEasyMDEImagePaste(this.easyMDE, this.dropzone); + initEasyMDEPaste(this.easyMDE, this.dropzone); hideElem(this.textareaMarkdownToolbar); } diff --git a/web_src/js/features/comp/ImagePaste.js b/web_src/js/features/comp/Paste.js similarity index 72% rename from web_src/js/features/comp/ImagePaste.js rename to web_src/js/features/comp/Paste.js index b727880bc8..b26296d1fc 100644 --- a/web_src/js/features/comp/ImagePaste.js +++ b/web_src/js/features/comp/Paste.js @@ -1,6 +1,8 @@ import {htmlEscape} from 'escape-goat'; import {POST} from '../../modules/fetch.js'; import {imageInfo} from '../../utils/image.js'; +import {getPastedContent, replaceTextareaSelection} from '../../utils/dom.js'; +import {isUrl} from '../../utils/url.js'; async function uploadFile(file, uploadUrl) { const formData = new FormData(); @@ -10,17 +12,6 @@ async function uploadFile(file, uploadUrl) { return await res.json(); } -function clipboardPastedImages(e) { - if (!e.clipboardData) return []; - - const files = []; - for (const item of e.clipboardData.items || []) { - if (!item.type || !item.type.startsWith('image/')) continue; - files.push(item.getAsFile()); - } - return files; -} - function triggerEditorContentChanged(target) { target.dispatchEvent(new CustomEvent('ce-editor-content-changed', {bubbles: true})); } @@ -91,20 +82,16 @@ class CodeMirrorEditor { } } -const uploadClipboardImage = async (editor, dropzone, e) => { +async function handleClipboardImages(editor, dropzone, images, e) { const uploadUrl = dropzone.getAttribute('data-upload-url'); const filesContainer = dropzone.querySelector('.files'); - if (!uploadUrl || !filesContainer) return; + if (!dropzone || !uploadUrl || !filesContainer || !images.length) return; - const pastedImages = clipboardPastedImages(e); - if (!pastedImages || pastedImages.length === 0) { - return; - } e.preventDefault(); e.stopPropagation(); - for (const img of pastedImages) { + for (const img of images) { const name = img.name.slice(0, img.name.lastIndexOf('.')); const placeholder = ``; @@ -131,18 +118,37 @@ const uploadClipboardImage = async (editor, dropzone, e) => { input.value = uuid; filesContainer.append(input); } -}; +} -export function initEasyMDEImagePaste(easyMDE, dropzone) { - if (!dropzone) return; - easyMDE.codemirror.on('paste', async (_, e) => { - return uploadClipboardImage(new CodeMirrorEditor(easyMDE.codemirror), dropzone, e); +function handleClipboardText(textarea, text, e) { + // when pasting links over selected text, turn it into [text](link), except when shift key is held + const {value, selectionStart, selectionEnd, _shiftDown} = textarea; + if (_shiftDown) return; + const selectedText = value.substring(selectionStart, selectionEnd); + const trimmedText = text.trim(); + if (selectedText && isUrl(trimmedText)) { + e.stopPropagation(); + e.preventDefault(); + replaceTextareaSelection(textarea, `[${selectedText}](${trimmedText})`); + } +} + +export function initEasyMDEPaste(easyMDE, dropzone) { + easyMDE.codemirror.on('paste', (_, e) => { + const {images} = getPastedContent(e); + if (images.length) { + handleClipboardImages(new CodeMirrorEditor(easyMDE.codemirror), dropzone, images, e); + } }); } -export function initTextareaImagePaste(textarea, dropzone) { - if (!dropzone) return; - textarea.addEventListener('paste', async (e) => { - return uploadClipboardImage(new TextareaEditor(textarea), dropzone, e); +export function initTextareaPaste(textarea, dropzone) { + textarea.addEventListener('paste', (e) => { + const {images, text} = getPastedContent(e); + if (images.length) { + handleClipboardImages(new TextareaEditor(textarea), dropzone, images, e); + } else if (text) { + handleClipboardText(textarea, text, e); + } }); } diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js index f9f5737554..016c3b55f0 100644 --- a/web_src/js/utils/dom.js +++ b/web_src/js/utils/dom.js @@ -242,3 +242,39 @@ export function isElemVisible(element) { return Boolean(element.offsetWidth || element.offsetHeight || element.getClientRects().length); } + +// extract text and images from "paste" event +export function getPastedContent(e) { + const images = []; + for (const item of e.clipboardData?.items ?? []) { + if (item.type?.startsWith('image/')) { + images.push(item.getAsFile()); + } + } + const text = e.clipboardData?.getData?.('text') ?? ''; + return {text, images}; +} + +// replace selected text in a textarea while preserving editor history, e.g. CTRL-Z works after this +export function replaceTextareaSelection(textarea, text) { + const before = textarea.value.slice(0, textarea.selectionStart ?? undefined); + const after = textarea.value.slice(textarea.selectionEnd ?? undefined); + let success = true; + + textarea.contentEditable = 'true'; + try { + success = document.execCommand('insertText', false, text); + } catch { + success = false; + } + textarea.contentEditable = 'false'; + + if (success && !textarea.value.slice(0, textarea.selectionStart ?? undefined).endsWith(text)) { + success = false; + } + + if (!success) { + textarea.value = `${before}${text}${after}`; + textarea.dispatchEvent(new CustomEvent('change', {bubbles: true, cancelable: true})); + } +} diff --git a/web_src/js/utils/url.js b/web_src/js/utils/url.js index a40737ca6f..470ece31b0 100644 --- a/web_src/js/utils/url.js +++ b/web_src/js/utils/url.js @@ -1,3 +1,15 @@ export function pathEscapeSegments(s) { return s.split('/').map(encodeURIComponent).join('/'); } + +function stripSlash(url) { + return url.endsWith('/') ? url.slice(0, -1) : url; +} + +export function isUrl(url) { + try { + return stripSlash((new URL(url).href)).trim() === stripSlash(url).trim(); + } catch { + return false; + } +} diff --git a/web_src/js/utils/url.test.js b/web_src/js/utils/url.test.js index 3dbedec94f..08c6373ffb 100644 --- a/web_src/js/utils/url.test.js +++ b/web_src/js/utils/url.test.js @@ -1,6 +1,13 @@ -import {pathEscapeSegments} from './url.js'; +import {pathEscapeSegments, isUrl} from './url.js'; test('pathEscapeSegments', () => { expect(pathEscapeSegments('a/b/c')).toEqual('a/b/c'); expect(pathEscapeSegments('a/b/ c')).toEqual('a/b/%20c'); }); + +test('isUrl', () => { + expect(isUrl('https://example.com')).toEqual(true); + expect(isUrl('https://example.com/')).toEqual(true); + expect(isUrl('https://example.com/index.html')).toEqual(true); + expect(isUrl('/index.html')).toEqual(false); +}); From e731e0c3469fee4794283af5e2b22773187297ff Mon Sep 17 00:00:00 2001 From: charles <30816317+charles7668@users.noreply.github.com> Date: Fri, 8 Mar 2024 23:43:48 +0800 Subject: [PATCH 48/62] Fix commit_status problem when testing (#29672) Close #29661 fix #29656 Co-authored-by: Giteabot <teabot@gitea.io> (cherry picked from commit 0c273f12e0b5d6dc2b80e2e6b51b2deaf15608b1) --- services/pull/commit_status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/pull/commit_status.go b/services/pull/commit_status.go index fdae8424a3..514bcee8f1 100644 --- a/services/pull/commit_status.go +++ b/services/pull/commit_status.go @@ -60,7 +60,7 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus, } } - if matchedCount == 0 { + if matchedCount == 0 && returnedStatus == structs.CommitStatusSuccess { status := git_model.CalcCommitStatus(commitStatuses) if status != nil { return status.State From 08027847441f02f8a626dc44128f85d9a18d2269 Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Fri, 8 Mar 2024 22:02:05 +0100 Subject: [PATCH 49/62] Replace more gt- with tw- (#29678) This will conclude the trivial class replacements. (cherry picked from commit 82e102f8b09faf1ac2786ccad36d4a20fcb392b8) --- services/auth/source/oauth2/providers.go | 2 +- templates/repo/commit_statuses.tmpl | 2 +- .../repo/issue/view_content/comments.tmpl | 8 +++---- templates/repo/pulse.tmpl | 12 +++++----- templates/shared/issuelist.tmpl | 6 ++--- templates/user/dashboard/issues.tmpl | 2 +- templates/user/dashboard/milestones.tmpl | 2 +- web_src/css/helpers.css | 23 ------------------- 8 files changed, 17 insertions(+), 40 deletions(-) diff --git a/services/auth/source/oauth2/providers.go b/services/auth/source/oauth2/providers.go index ac32647839..c3edae4ab6 100644 --- a/services/auth/source/oauth2/providers.go +++ b/services/auth/source/oauth2/providers.go @@ -59,7 +59,7 @@ func (p *AuthSourceProvider) DisplayName() string { func (p *AuthSourceProvider) IconHTML(size int) template.HTML { if p.iconURL != "" { - img := fmt.Sprintf(`<img class="gt-object-contain gt-mr-3" width="%d" height="%d" src="%s" alt="%s">`, + img := fmt.Sprintf(`<img class="tw-object-contain gt-mr-3" width="%d" height="%d" src="%s" alt="%s">`, size, size, html.EscapeString(p.iconURL), html.EscapeString(p.DisplayName()), diff --git a/templates/repo/commit_statuses.tmpl b/templates/repo/commit_statuses.tmpl index 74c20a6a2c..b035e74c2f 100644 --- a/templates/repo/commit_statuses.tmpl +++ b/templates/repo/commit_statuses.tmpl @@ -1,6 +1,6 @@ {{if .Statuses}} {{if and (eq (len .Statuses) 1) .Status.TargetURL}} - <a class="gt-vm {{.AdditionalClasses}} gt-no-underline" data-tippy="commit-statuses" href="{{.Status.TargetURL}}"> + <a class="gt-vm {{.AdditionalClasses}} tw-no-underline" data-tippy="commit-statuses" href="{{.Status.TargetURL}}"> {{template "repo/commit_status" .Status}} </a> {{else}} diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index bc2fd48e67..37ec98a3de 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -80,7 +80,7 @@ </div> {{else if eq .Type 1}} <div class="timeline-item event" id="{{.HashTag}}"> - <span class="badge gt-bg-green gt-text-white">{{svg "octicon-dot-fill"}}</span> + <span class="badge tw-bg-green tw-text-white">{{svg "octicon-dot-fill"}}</span> {{if not .OriginalAuthor}} {{template "shared/user/avatarlink" dict "user" .Poster}} {{end}} @@ -95,7 +95,7 @@ </div> {{else if eq .Type 2}} <div class="timeline-item event" id="{{.HashTag}}"> - <span class="badge gt-bg-red gt-text-white">{{svg "octicon-circle-slash"}}</span> + <span class="badge tw-bg-red tw-text-white">{{svg "octicon-circle-slash"}}</span> {{if not .OriginalAuthor}} {{template "shared/user/avatarlink" dict "user" .Poster}} {{end}} @@ -110,7 +110,7 @@ </div> {{else if eq .Type 28}} <div class="timeline-item event" id="{{.HashTag}}"> - <span class="badge gt-bg-purple gt-text-white">{{svg "octicon-git-merge"}}</span> + <span class="badge tw-bg-purple tw-text-white">{{svg "octicon-git-merge"}}</span> {{if not .OriginalAuthor}} {{template "shared/user/avatarlink" dict "user" .Poster}} {{end}} @@ -383,7 +383,7 @@ {{ctx.AvatarUtils.Avatar .Poster 40}} </a> {{end}} - <span class="badge{{if eq .Review.Type 1}} gt-bg-green gt-text-white{{else if eq .Review.Type 3}} gt-bg-red gt-text-white{{end}}">{{svg (printf "octicon-%s" .Review.Type.Icon)}}</span> + <span class="badge{{if eq .Review.Type 1}} tw-bg-green tw-text-white{{else if eq .Review.Type 3}} tw-bg-red tw-text-white{{end}}">{{svg (printf "octicon-%s" .Review.Type.Icon)}}</span> <span class="text grey muted-links"> {{template "repo/issue/view_content/comments_authorlink" dict "ctxData" $ "comment" .}} {{if eq .Review.Type 1}} diff --git a/templates/repo/pulse.tmpl b/templates/repo/pulse.tmpl index e6a59ea8c6..5943ae0434 100644 --- a/templates/repo/pulse.tmpl +++ b/templates/repo/pulse.tmpl @@ -108,7 +108,7 @@ {{end}} {{if gt .Activity.PublishedReleaseCount 0}} - <h4 class="divider divider-text gt-normal-case" id="published-releases"> + <h4 class="divider divider-text tw-normal-case" id="published-releases"> {{svg "octicon-tag" 16 "gt-mr-3"}} {{ctx.Locale.Tr "repo.activity.title.releases_published_by" (ctx.Locale.TrN .Activity.PublishedReleaseCount "repo.activity.title.releases_1" "repo.activity.title.releases_n" .Activity.PublishedReleaseCount) @@ -130,7 +130,7 @@ {{end}} {{if gt .Activity.MergedPRCount 0}} - <h4 class="divider divider-text gt-normal-case" id="merged-pull-requests"> + <h4 class="divider divider-text tw-normal-case" id="merged-pull-requests"> {{svg "octicon-git-pull-request" 16 "gt-mr-3"}} {{ctx.Locale.Tr "repo.activity.title.prs_merged_by" (ctx.Locale.TrN .Activity.MergedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.MergedPRCount) @@ -149,7 +149,7 @@ {{end}} {{if gt .Activity.OpenedPRCount 0}} - <h4 class="divider divider-text gt-normal-case" id="proposed-pull-requests"> + <h4 class="divider divider-text tw-normal-case" id="proposed-pull-requests"> {{svg "octicon-git-branch" 16 "gt-mr-3"}} {{ctx.Locale.Tr "repo.activity.title.prs_opened_by" (ctx.Locale.TrN .Activity.OpenedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.OpenedPRCount) @@ -168,7 +168,7 @@ {{end}} {{if gt .Activity.ClosedIssueCount 0}} - <h4 class="divider divider-text gt-normal-case" id="closed-issues"> + <h4 class="divider divider-text tw-normal-case" id="closed-issues"> {{svg "octicon-issue-closed" 16 "gt-mr-3"}} {{ctx.Locale.Tr "repo.activity.title.issues_closed_from" (ctx.Locale.TrN .Activity.ClosedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.ClosedIssueCount) @@ -187,7 +187,7 @@ {{end}} {{if gt .Activity.OpenedIssueCount 0}} - <h4 class="divider divider-text gt-normal-case" id="new-issues"> + <h4 class="divider divider-text tw-normal-case" id="new-issues"> {{svg "octicon-issue-opened" 16 "gt-mr-3"}} {{ctx.Locale.Tr "repo.activity.title.issues_created_by" (ctx.Locale.TrN .Activity.OpenedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.OpenedIssueCount) @@ -206,7 +206,7 @@ {{end}} {{if gt .Activity.UnresolvedIssueCount 0}} - <h4 class="divider divider-text gt-normal-case" id="unresolved-conversations" data-tooltip-content="{{ctx.Locale.Tr "repo.activity.unresolved_conv_desc"}}"> + <h4 class="divider divider-text tw-normal-case" id="unresolved-conversations" data-tooltip-content="{{ctx.Locale.Tr "repo.activity.unresolved_conv_desc"}}"> {{svg "octicon-comment-discussion" 16 "gt-mr-3"}} {{ctx.Locale.TrN .Activity.UnresolvedIssueCount "repo.activity.title.unresolved_conv_1" "repo.activity.title.unresolved_conv_n" .Activity.UnresolvedIssueCount}} </h4> diff --git a/templates/shared/issuelist.tmpl b/templates/shared/issuelist.tmpl index e8a0079c1c..a90188297f 100644 --- a/templates/shared/issuelist.tmpl +++ b/templates/shared/issuelist.tmpl @@ -13,7 +13,7 @@ <div class="flex-item-main"> <div class="flex-item-header"> <div class="flex-item-title"> - <a class="gt-no-underline issue-title" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">{{RenderEmoji $.Context .Title | RenderCodeBlock}}</a> + <a class="tw-no-underline issue-title" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">{{RenderEmoji $.Context .Title | RenderCodeBlock}}</a> {{if .IsPull}} {{if (index $.CommitStatuses .PullRequest.ID)}} {{template "repo/commit_statuses" dict "Status" (index $.CommitLastStatus .PullRequest.ID) "Statuses" (index $.CommitStatuses .PullRequest.ID)}} @@ -36,7 +36,7 @@ {{if .Assignees}} <div class="text grey"> {{range .Assignees}} - <a class="ui assignee gt-no-underline" href="{{.HomeLink}}" data-tooltip-content="{{.GetDisplayName}}"> + <a class="ui assignee tw-no-underline" href="{{.HomeLink}}" data-tooltip-content="{{.GetDisplayName}}"> {{ctx.AvatarUtils.Avatar . 20}} </a> {{end}} @@ -44,7 +44,7 @@ {{end}} {{if .NumComments}} <div class="text grey"> - <a class="gt-no-underline muted flex-text-block" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}"> + <a class="tw-no-underline muted flex-text-block" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}"> {{svg "octicon-comment" 16}}{{.NumComments}} </a> </div> diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index fd5960c31e..7b7023cfaa 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -4,7 +4,7 @@ <div class="ui container"> <div class="flex-container"> <div class="flex-container-nav"> - <div class="ui secondary vertical filter menu gt-bg-transparent"> + <div class="ui secondary vertical filter menu tw-bg-transparent"> <a class="{{if eq .ViewType "your_repositories"}}active{{end}} item" href="{{.Link}}?type=your_repositories&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}"> {{ctx.Locale.Tr "home.issues.in_your_repos"}} <strong>{{CountFmt .IssueStats.YourRepositoriesCount}}</strong> diff --git a/templates/user/dashboard/milestones.tmpl b/templates/user/dashboard/milestones.tmpl index 737a0f7e2b..7cde02291b 100644 --- a/templates/user/dashboard/milestones.tmpl +++ b/templates/user/dashboard/milestones.tmpl @@ -4,7 +4,7 @@ <div class="ui container"> <div class="flex-container"> <div class="flex-container-nav"> - <div class="ui secondary vertical filter menu gt-bg-transparent"> + <div class="ui secondary vertical filter menu tw-bg-transparent"> <div class="item"> {{ctx.Locale.Tr "home.issues.in_your_repos"}} <strong>{{.Total}}</strong> diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css index dad0f9b127..860722823a 100644 --- a/web_src/css/helpers.css +++ b/web_src/css/helpers.css @@ -46,12 +46,6 @@ Gitea's private styles use `g-` prefix. text-overflow: ellipsis; } -/* below class names match Tailwind CSS */ -.gt-object-contain { object-fit: contain !important; } -.gt-no-underline { text-decoration-line: none !important; } -.gt-normal-case { text-transform: none !important; } -.gt-italic { font-style: italic !important; } - .gt-font-light { font-weight: var(--font-weight-light) !important; } .gt-font-normal { font-weight: var(--font-weight-normal) !important; } .gt-font-medium { font-weight: var(--font-weight-medium) !important; } @@ -70,23 +64,6 @@ Gitea's private styles use `g-` prefix. .gt-border-secondary-left { border-left: 1px solid var(--color-secondary) !important; } .gt-border-secondary-right { border-right: 1px solid var(--color-secondary) !important; } -.gt-bg-red { background: var(--color-red) !important; } -.gt-bg-orange { background: var(--color-orange) !important; } -.gt-bg-yellow { background: var(--color-yellow) !important; } -.gt-bg-olive { background: var(--color-olive) !important; } -.gt-bg-green { background: var(--color-green) !important; } -.gt-bg-teal { background: var(--color-teal) !important; } -.gt-bg-blue { background: var(--color-blue) !important; } -.gt-bg-violet { background: var(--color-violet) !important; } -.gt-bg-purple { background: var(--color-purple) !important; } -.gt-bg-pink { background: var(--color-pink) !important; } -.gt-bg-brown { background: var(--color-brown) !important; } -.gt-bg-grey { background: var(--color-grey) !important; } -.gt-bg-gold { background: var(--color-gold) !important; } -.gt-bg-transparent { background: transparent !important; } - -.gt-text-white { color: var(--color-white) !important; } - .interact-fg { color: inherit !important; } .interact-fg:hover { color: var(--color-primary) !important; } .interact-fg:active { color: var(--color-primary-active) !important; } From 299c2a1408d4b8efa897a9d6d8941ab8352ce3e8 Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Sat, 9 Mar 2024 00:21:45 +0100 Subject: [PATCH 50/62] Use more specific selector for `name` links (#29679) Followup https://github.com/go-gitea/gitea/pull/29305. As per discussion in https://github.com/go-gitea/gitea/pull/29666#discussion_r1517506422, make this selector only search in the current `.markup` document, as there can be multiples displayed at the same time. @DanielMatiasCarvalho maybe you can review. (cherry picked from commit baeb2511741aa70d24a48fd46db936b52be9d9dd) --- web_src/js/markup/anchors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/markup/anchors.js b/web_src/js/markup/anchors.js index 03934ea215..6cf83eb428 100644 --- a/web_src/js/markup/anchors.js +++ b/web_src/js/markup/anchors.js @@ -39,7 +39,7 @@ export function initMarkupAnchors() { if (!href.startsWith('#user-content-')) continue; const originalId = href.replace(/^#user-content-/, ''); a.setAttribute('href', `#${encodeURIComponent(originalId)}`); - if (document.getElementsByName(originalId).length !== 1) { + if (a.closest('.markup').querySelectorAll(`a[name="${originalId}"]`).length !== 1) { a.addEventListener('click', (e) => { scrollToAnchor(e.currentTarget.getAttribute('href'), false); }); From 38c3cc4eb7e59c97823bb9f0322be61774c60d0f Mon Sep 17 00:00:00 2001 From: 6543 <m.huber@kithara.com> Date: Sat, 9 Mar 2024 02:39:27 +0100 Subject: [PATCH 51/62] Patch in exact search for meilisearch (#29671) meilisearch does not have an search option to contorl fuzzynes per query right now: - https://github.com/meilisearch/meilisearch/issues/1192 - https://github.com/orgs/meilisearch/discussions/377 - https://github.com/meilisearch/meilisearch/discussions/1096 so we have to create a workaround by post-filter the search result in gitea until this is addressed. For future works I added an option in backend only atm, to enable fuzzynes for issue indexer too. And also refactored the code so the fuzzy option is equal in logic to code indexer --- *Sponsored by Kithara Software GmbH* Conflicts: routers/web/repo/search.go trivial context confict s/isMatch/isFuzzy/ --- modules/indexer/code/bleve/bleve.go | 12 +-- .../code/elasticsearch/elasticsearch.go | 8 +- modules/indexer/code/indexer_test.go | 2 +- modules/indexer/code/internal/indexer.go | 4 +- modules/indexer/code/search.go | 5 +- modules/indexer/internal/bleve/query.go | 7 ++ modules/indexer/issues/bleve/bleve.go | 17 +++- .../issues/elasticsearch/elasticsearch.go | 12 ++- modules/indexer/issues/internal/model.go | 2 + .../indexer/issues/meilisearch/meilisearch.go | 91 +++++++++++++++++-- .../issues/meilisearch/meilisearch_test.go | 45 +++++++++ routers/web/explore/code.go | 4 +- routers/web/repo/search.go | 4 +- routers/web/user/code.go | 4 +- 14 files changed, 184 insertions(+), 33 deletions(-) diff --git a/modules/indexer/code/bleve/bleve.go b/modules/indexer/code/bleve/bleve.go index 8ba50ed77c..107dd23598 100644 --- a/modules/indexer/code/bleve/bleve.go +++ b/modules/indexer/code/bleve/bleve.go @@ -233,21 +233,21 @@ func (b *Indexer) Delete(_ context.Context, repoID int64) error { // Search searches for files in the specified repo. // Returns the matching file-paths -func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isMatch bool) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) { +func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) { var ( indexerQuery query.Query keywordQuery query.Query ) - if isMatch { - prefixQuery := bleve.NewPrefixQuery(keyword) - prefixQuery.FieldVal = "Content" - keywordQuery = prefixQuery - } else { + if isFuzzy { phraseQuery := bleve.NewMatchPhraseQuery(keyword) phraseQuery.FieldVal = "Content" phraseQuery.Analyzer = repoIndexerAnalyzer keywordQuery = phraseQuery + } else { + prefixQuery := bleve.NewPrefixQuery(keyword) + prefixQuery.FieldVal = "Content" + keywordQuery = prefixQuery } if len(repoIDs) > 0 { diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go index 0f70f13485..065b0b2061 100644 --- a/modules/indexer/code/elasticsearch/elasticsearch.go +++ b/modules/indexer/code/elasticsearch/elasticsearch.go @@ -281,10 +281,10 @@ func extractAggs(searchResult *elastic.SearchResult) []*internal.SearchResultLan } // Search searches for codes and language stats by given conditions. -func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isMatch bool) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) { - searchType := esMultiMatchTypeBestFields - if isMatch { - searchType = esMultiMatchTypePhrasePrefix +func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) { + searchType := esMultiMatchTypePhrasePrefix + if isFuzzy { + searchType = esMultiMatchTypeBestFields } kwQuery := elastic.NewMultiMatchQuery(keyword, "content").Type(searchType) diff --git a/modules/indexer/code/indexer_test.go b/modules/indexer/code/indexer_test.go index 5eb8e61e3d..23dbd63410 100644 --- a/modules/indexer/code/indexer_test.go +++ b/modules/indexer/code/indexer_test.go @@ -70,7 +70,7 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) { for _, kw := range keywords { t.Run(kw.Keyword, func(t *testing.T) { - total, res, langs, err := indexer.Search(context.TODO(), kw.RepoIDs, "", kw.Keyword, 1, 10, false) + total, res, langs, err := indexer.Search(context.TODO(), kw.RepoIDs, "", kw.Keyword, 1, 10, true) assert.NoError(t, err) assert.Len(t, kw.IDs, int(total)) assert.Len(t, langs, kw.Langs) diff --git a/modules/indexer/code/internal/indexer.go b/modules/indexer/code/internal/indexer.go index da3ac3623c..c92419deb2 100644 --- a/modules/indexer/code/internal/indexer.go +++ b/modules/indexer/code/internal/indexer.go @@ -16,7 +16,7 @@ type Indexer interface { internal.Indexer Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *RepoChanges) error Delete(ctx context.Context, repoID int64) error - Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isMatch bool) (int64, []*SearchResult, []*SearchResultLanguages, error) + Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int64, []*SearchResult, []*SearchResultLanguages, error) } // NewDummyIndexer returns a dummy indexer @@ -38,6 +38,6 @@ func (d *dummyIndexer) Delete(ctx context.Context, repoID int64) error { return fmt.Errorf("indexer is not ready") } -func (d *dummyIndexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isMatch bool) (int64, []*SearchResult, []*SearchResultLanguages, error) { +func (d *dummyIndexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int64, []*SearchResult, []*SearchResultLanguages, error) { return 0, nil, nil, fmt.Errorf("indexer is not ready") } diff --git a/modules/indexer/code/search.go b/modules/indexer/code/search.go index 2ddc2397fa..89a62a8d3e 100644 --- a/modules/indexer/code/search.go +++ b/modules/indexer/code/search.go @@ -124,12 +124,13 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res } // PerformSearch perform a search on a repository -func PerformSearch(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isMatch bool) (int, []*Result, []*internal.SearchResultLanguages, error) { +// if isFuzzy is true set the Damerau-Levenshtein distance from 0 to 2 +func PerformSearch(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int, []*Result, []*internal.SearchResultLanguages, error) { if len(keyword) == 0 { return 0, nil, nil, nil } - total, results, resultLanguages, err := (*globalIndexer.Load()).Search(ctx, repoIDs, language, keyword, page, pageSize, isMatch) + total, results, resultLanguages, err := (*globalIndexer.Load()).Search(ctx, repoIDs, language, keyword, page, pageSize, isFuzzy) if err != nil { return 0, nil, nil, err } diff --git a/modules/indexer/internal/bleve/query.go b/modules/indexer/internal/bleve/query.go index c7d66538c1..2a427c4020 100644 --- a/modules/indexer/internal/bleve/query.go +++ b/modules/indexer/internal/bleve/query.go @@ -25,6 +25,13 @@ func MatchPhraseQuery(matchPhrase, field, analyzer string) *query.MatchPhraseQue return q } +// PrefixQuery generates a match prefix query for the given prefix and field +func PrefixQuery(matchPrefix, field string) *query.PrefixQuery { + q := bleve.NewPrefixQuery(matchPrefix) + q.FieldVal = field + return q +} + // BoolFieldQuery generates a bool field query for the given value and field func BoolFieldQuery(value bool, field string) *query.BoolFieldQuery { q := bleve.NewBoolFieldQuery(value) diff --git a/modules/indexer/issues/bleve/bleve.go b/modules/indexer/issues/bleve/bleve.go index 6a5d65cb66..aaea854efa 100644 --- a/modules/indexer/issues/bleve/bleve.go +++ b/modules/indexer/issues/bleve/bleve.go @@ -156,12 +156,19 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( var queries []query.Query if options.Keyword != "" { - keywordQueries := []query.Query{ - inner_bleve.MatchPhraseQuery(options.Keyword, "title", issueIndexerAnalyzer), - inner_bleve.MatchPhraseQuery(options.Keyword, "content", issueIndexerAnalyzer), - inner_bleve.MatchPhraseQuery(options.Keyword, "comments", issueIndexerAnalyzer), + if options.IsFuzzyKeyword { + queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{ + inner_bleve.MatchPhraseQuery(options.Keyword, "title", issueIndexerAnalyzer), + inner_bleve.MatchPhraseQuery(options.Keyword, "content", issueIndexerAnalyzer), + inner_bleve.MatchPhraseQuery(options.Keyword, "comments", issueIndexerAnalyzer), + }...)) + } else { + queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{ + inner_bleve.PrefixQuery(options.Keyword, "title"), + inner_bleve.PrefixQuery(options.Keyword, "content"), + inner_bleve.PrefixQuery(options.Keyword, "comments"), + }...)) } - queries = append(queries, bleve.NewDisjunctionQuery(keywordQueries...)) } if len(options.RepoIDs) > 0 || options.AllPublic { diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go index 3acd3ade71..0077da263a 100644 --- a/modules/indexer/issues/elasticsearch/elasticsearch.go +++ b/modules/indexer/issues/elasticsearch/elasticsearch.go @@ -19,6 +19,10 @@ import ( const ( issueIndexerLatestVersion = 1 + // multi-match-types, currently only 2 types are used + // Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types + esMultiMatchTypeBestFields = "best_fields" + esMultiMatchTypePhrasePrefix = "phrase_prefix" ) var _ internal.Indexer = &Indexer{} @@ -141,7 +145,13 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( query := elastic.NewBoolQuery() if options.Keyword != "" { - query.Must(elastic.NewMultiMatchQuery(options.Keyword, "title", "content", "comments")) + + searchType := esMultiMatchTypePhrasePrefix + if options.IsFuzzyKeyword { + searchType = esMultiMatchTypeBestFields + } + + query.Must(elastic.NewMultiMatchQuery(options.Keyword, "title", "content", "comments").Type(searchType)) } if len(options.RepoIDs) > 0 { diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go index 947335d8ce..d41fec4aba 100644 --- a/modules/indexer/issues/internal/model.go +++ b/modules/indexer/issues/internal/model.go @@ -74,6 +74,8 @@ type SearchResult struct { type SearchOptions struct { Keyword string // keyword to search + IsFuzzyKeyword bool // if false the levenshtein distance is 0 + RepoIDs []int64 // repository IDs which the issues belong to AllPublic bool // if include all public repositories diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go index 325883196b..c429920065 100644 --- a/modules/indexer/issues/meilisearch/meilisearch.go +++ b/modules/indexer/issues/meilisearch/meilisearch.go @@ -5,6 +5,7 @@ package meilisearch import ( "context" + "errors" "strconv" "strings" @@ -16,12 +17,15 @@ import ( ) const ( - issueIndexerLatestVersion = 2 + issueIndexerLatestVersion = 3 // TODO: make this configurable if necessary maxTotalHits = 10000 ) +// ErrMalformedResponse is never expected as we initialize the indexer ourself and so define the types. +var ErrMalformedResponse = errors.New("meilisearch returned unexpected malformed content") + var _ internal.Indexer = &Indexer{} // Indexer implements Indexer interface @@ -47,6 +51,9 @@ func NewIndexer(url, apiKey, indexerName string) *Indexer { }, DisplayedAttributes: []string{ "id", + "title", + "content", + "comments", }, FilterableAttributes: []string{ "repo_id", @@ -221,11 +228,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( return nil, err } - hits := make([]internal.Match, 0, len(searchRes.Hits)) - for _, hit := range searchRes.Hits { - hits = append(hits, internal.Match{ - ID: int64(hit.(map[string]any)["id"].(float64)), - }) + hits, err := nonFuzzyWorkaround(searchRes, options.Keyword, options.IsFuzzyKeyword) + if err != nil { + return nil, err } return &internal.SearchResult{ @@ -241,3 +246,77 @@ func parseSortBy(sortBy internal.SortBy) string { } return field + ":asc" } + +// nonFuzzyWorkaround is needed as meilisearch does not have an exact search +// and you can only change "typo tolerance" per index. So we have to post-filter the results +// https://www.meilisearch.com/docs/learn/configuration/typo_tolerance#configuring-typo-tolerance +// TODO: remove once https://github.com/orgs/meilisearch/discussions/377 is addressed +func nonFuzzyWorkaround(searchRes *meilisearch.SearchResponse, keyword string, isFuzzy bool) ([]internal.Match, error) { + hits := make([]internal.Match, 0, len(searchRes.Hits)) + for _, hit := range searchRes.Hits { + hit, ok := hit.(map[string]any) + if !ok { + return nil, ErrMalformedResponse + } + + if !isFuzzy { + keyword = strings.ToLower(keyword) + + // declare a anon func to check if the title, content or at least one comment contains the keyword + found, err := func() (bool, error) { + // check if title match first + title, ok := hit["title"].(string) + if !ok { + return false, ErrMalformedResponse + } else if strings.Contains(strings.ToLower(title), keyword) { + return true, nil + } + + // check if content has a match + content, ok := hit["content"].(string) + if !ok { + return false, ErrMalformedResponse + } else if strings.Contains(strings.ToLower(content), keyword) { + return true, nil + } + + // now check for each comment if one has a match + // so we first try to cast and skip if there are no comments + comments, ok := hit["comments"].([]any) + if !ok { + return false, ErrMalformedResponse + } else if len(comments) == 0 { + return false, nil + } + + // now we iterate over all and report as soon as we detect one match + for i := range comments { + comment, ok := comments[i].(string) + if !ok { + return false, ErrMalformedResponse + } + if strings.Contains(strings.ToLower(comment), keyword) { + return true, nil + } + } + + // we got no match + return false, nil + }() + + if err != nil { + return nil, err + } else if !found { + continue + } + } + issueID, ok := hit["id"].(float64) + if !ok { + return nil, ErrMalformedResponse + } + hits = append(hits, internal.Match{ + ID: int64(issueID), + }) + } + return hits, nil +} diff --git a/modules/indexer/issues/meilisearch/meilisearch_test.go b/modules/indexer/issues/meilisearch/meilisearch_test.go index 8a6b0a61d3..1a9bbeef10 100644 --- a/modules/indexer/issues/meilisearch/meilisearch_test.go +++ b/modules/indexer/issues/meilisearch/meilisearch_test.go @@ -10,7 +10,11 @@ import ( "testing" "time" + "code.gitea.io/gitea/modules/indexer/issues/internal" "code.gitea.io/gitea/modules/indexer/issues/internal/tests" + + "github.com/meilisearch/meilisearch-go" + "github.com/stretchr/testify/assert" ) func TestMeilisearchIndexer(t *testing.T) { @@ -49,3 +53,44 @@ func TestMeilisearchIndexer(t *testing.T) { tests.TestIndexer(t, indexer) } + +func TestNonFuzzyWorkaround(t *testing.T) { + // get unexpected return + _, err := nonFuzzyWorkaround(&meilisearch.SearchResponse{ + Hits: []any{"aa", "bb", "cc", "dd"}, + }, "bowling", false) + assert.ErrorIs(t, err, ErrMalformedResponse) + + validResponse := &meilisearch.SearchResponse{ + Hits: []any{ + map[string]any{ + "id": float64(11), + "title": "a title", + "content": "issue body with no match", + "comments": []any{"hey whats up?", "I'm currently bowling", "nice"}, + }, + map[string]any{ + "id": float64(22), + "title": "Bowling as title", + "content": "", + "comments": []any{}, + }, + map[string]any{ + "id": float64(33), + "title": "Bowl-ing as fuzzy match", + "content": "", + "comments": []any{}, + }, + }, + } + + // nonFuzzy + hits, err := nonFuzzyWorkaround(validResponse, "bowling", false) + assert.NoError(t, err) + assert.EqualValues(t, []internal.Match{{ID: 11}, {ID: 22}}, hits) + + // fuzzy + hits, err = nonFuzzyWorkaround(validResponse, "bowling", true) + assert.NoError(t, err) + assert.EqualValues(t, []internal.Match{{ID: 11}, {ID: 22}, {ID: 33}}, hits) +} diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go index 2cde8b655e..a6bc71ac9c 100644 --- a/routers/web/explore/code.go +++ b/routers/web/explore/code.go @@ -35,7 +35,7 @@ func Code(ctx *context.Context) { keyword := ctx.FormTrim("q") queryType := ctx.FormTrim("t") - isMatch := queryType == "match" + isFuzzy := queryType != "match" ctx.Data["Keyword"] = keyword ctx.Data["Language"] = language @@ -77,7 +77,7 @@ func Code(ctx *context.Context) { ) if (len(repoIDs) > 0) || isAdmin { - total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch) + total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isFuzzy) if err != nil { if code_indexer.IsAvailable(ctx) { ctx.ServerError("SearchResults", err) diff --git a/routers/web/repo/search.go b/routers/web/repo/search.go index 74970c02c9..550a3dc8be 100644 --- a/routers/web/repo/search.go +++ b/routers/web/repo/search.go @@ -21,7 +21,7 @@ func Search(ctx *context.Context) { keyword := ctx.FormTrim("q") queryType := ctx.FormTrim("t") - isMatch := queryType == "match" + isFuzzy := queryType != "match" ctx.Data["Keyword"] = keyword ctx.Data["Language"] = language @@ -44,7 +44,7 @@ func Search(ctx *context.Context) { ctx.Data["CodeIndexerEnabled"] = true total, searchResults, searchResultLanguages, err := code_indexer.PerformSearch(ctx, []int64{ctx.Repo.Repository.ID}, - language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch) + language, keyword, page, setting.UI.RepoSearchPagingNum, isFuzzy) if err != nil { if code_indexer.IsAvailable(ctx) { ctx.ServerError("SearchResults", err) diff --git a/routers/web/user/code.go b/routers/web/user/code.go index eb711b76eb..8613d38b65 100644 --- a/routers/web/user/code.go +++ b/routers/web/user/code.go @@ -40,7 +40,7 @@ func CodeSearch(ctx *context.Context) { keyword := ctx.FormTrim("q") queryType := ctx.FormTrim("t") - isMatch := queryType == "match" + isFuzzy := queryType != "match" ctx.Data["Keyword"] = keyword ctx.Data["Language"] = language @@ -75,7 +75,7 @@ func CodeSearch(ctx *context.Context) { ) if len(repoIDs) > 0 { - total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch) + total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isFuzzy) if err != nil { if code_indexer.IsAvailable(ctx) { ctx.ServerError("SearchResults", err) From 249e26fdae8b3e04b5b5c13f1a5bdb5b3a6671ef Mon Sep 17 00:00:00 2001 From: Yarden Shoham <git@yardenshoham.com> Date: Sat, 9 Mar 2024 13:59:16 +0200 Subject: [PATCH 52/62] Fix WebHookEditor regression from jQuery removal (#29692) Make these calls optional --------- Signed-off-by: Yarden Shoham <git@yardenshoham.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> (cherry picked from commit 1dc7f5338623ec97d9ea395380270470847a0066) --- web_src/js/features/comp/WebHookEditor.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/web_src/js/features/comp/WebHookEditor.js b/web_src/js/features/comp/WebHookEditor.js index 86d21dc815..b7ca5a0fcf 100644 --- a/web_src/js/features/comp/WebHookEditor.js +++ b/web_src/js/features/comp/WebHookEditor.js @@ -22,13 +22,16 @@ export function initCompWebHookEditor() { }); } - const updateContentType = function () { - const visible = document.getElementById('http_method').value === 'POST'; - toggleElem(document.getElementById('content_type').parentNode.parentNode, visible); - }; - updateContentType(); - - document.getElementById('http_method').addEventListener('change', updateContentType); + // some webhooks (like Gitea) allow to set the request method (GET/POST), and it would toggle the "Content Type" field + const httpMethodInput = document.getElementById('http_method'); + if (httpMethodInput) { + const updateContentType = function () { + const visible = httpMethodInput.value === 'POST'; + toggleElem(document.getElementById('content_type').closest('.field'), visible); + }; + updateContentType(); + httpMethodInput.addEventListener('change', updateContentType); + } // Test delivery document.getElementById('test-delivery')?.addEventListener('click', async function () { From 01bf3a82cfdd36ed599329426c26f6eecde19b5e Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Sat, 9 Mar 2024 13:09:22 +0100 Subject: [PATCH 53/62] Include go files in tailwind processing (#29686) We need to scan `.go` files for tailwind classes. Does not seem to affect build time much luckily. Fixes: https://github.com/go-gitea/gitea/pull/29678#discussion_r1518448600 Verified via `rg tw-object-contain public/assets/css/index.css`. --------- Co-authored-by: Giteabot <teabot@gitea.io> (cherry picked from commit 1695a5ac74afc51f38fd3a1def76cff6ba8d8641) --- tailwind.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tailwind.config.js b/tailwind.config.js index 63a5387d19..d783268bd7 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -31,6 +31,9 @@ export default { isProduction && '!./web_src/js/standalone/devtest.js', '!./templates/swagger/v1_json.tmpl', '!./templates/user/auth/oidc_wellknown.tmpl', + '!**/*_test.go', + '!./modules/{public,options,templates}/bindata.go', + './{build,models,modules,routers,services}/**/*.go', './templates/**/*.tmpl', './web_src/js/**/*.{js,vue}', ].filter(Boolean), From 9916f3ed64a715fb9a31a0fcad6452276e275615 Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Sat, 9 Mar 2024 13:14:42 +0100 Subject: [PATCH 54/62] Completely style the webkit autofill (#29683) Previously it was only partially styled, e.g. there was black text on white background even in dark theme caused by fomantic styles. <img width="195" alt="image" src="https://github.com/go-gitea/gitea/assets/115237/bc5cf516-2aef-45c3-854a-c9f5497aacca"> <img width="195" alt="Screenshot 2024-03-09 at 02 09 29" src="https://github.com/go-gitea/gitea/assets/115237/ef0af17d-6e0b-402e-b24d-bfa34dc2f4e0"> Co-authored-by: Giteabot <teabot@gitea.io> (cherry picked from commit 9b69f76e5a33788150f3abc3dee64010539c6b86) --- web_src/css/base.css | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/web_src/css/base.css b/web_src/css/base.css index 9f402d900d..cc74db820f 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -883,10 +883,18 @@ img.ui.avatar, border-color: var(--color-error-border) !important; } -/* A fix for text visibility issue in Chrome autofill in dark mode. */ -/* It's a problem from Formatic UI, and this rule overrides it. */ -.ui.form .field.field input:-webkit-autofill { - -webkit-text-fill-color: var(--color-black) !important; +input:-webkit-autofill, +input:-webkit-autofill:focus, +input:-webkit-autofill:hover, +input:-webkit-autofill:active, +.ui.form .field.field input:-webkit-autofill, +.ui.form .field.field input:-webkit-autofill:focus, +.ui.form .field.field input:-webkit-autofill:hover, +.ui.form .field.field input:-webkit-autofill:active { + -webkit-background-clip: text; + -webkit-text-fill-color: var(--color-text); + box-shadow: 0 0 0 100px var(--color-primary-light-6) inset !important; + border-color: var(--color-primary-light-4) !important; } .ui.form .field.muted { From 04b79bb48b490644c46e58da46af4b62a40e5e03 Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Sat, 9 Mar 2024 13:41:32 +0100 Subject: [PATCH 55/62] Update allowed attachment types (#29688) Update to match GitHub's latest. Co-authored-by: Giteabot <teabot@gitea.io> (cherry picked from commit 6ea1c67eadaf65079958cc4ad3b014966e47dd1a) --- docs/content/administration/config-cheat-sheet.en-us.md | 2 +- docs/content/administration/config-cheat-sheet.zh-cn.md | 2 +- modules/setting/attachment.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 8a01711949..43ec470ad0 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -832,7 +832,7 @@ Default templates for project boards: ## Issue and pull request attachments (`attachment`) - `ENABLED`: **true**: Whether issue and pull request attachments are enabled. -- `ALLOWED_TYPES`: **.csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. +- `ALLOWED_TYPES`: **.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. - `MAX_SIZE`: **2048**: Maximum size (MB). - `MAX_FILES`: **5**: Maximum number of attachments that can be uploaded at once. - `STORAGE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]` diff --git a/docs/content/administration/config-cheat-sheet.zh-cn.md b/docs/content/administration/config-cheat-sheet.zh-cn.md index 7b102eda8e..1f98db78aa 100644 --- a/docs/content/administration/config-cheat-sheet.zh-cn.md +++ b/docs/content/administration/config-cheat-sheet.zh-cn.md @@ -782,7 +782,7 @@ Gitea 创建以下非唯一队列: ## 工单和合并请求的附件 (`attachment`) - `ENABLED`: **true**: 是否允许用户上传附件。 -- `ALLOWED_TYPES`: **.csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: 允许的文件扩展名(`.zip`)、mime 类型(`text/plain`)或通配符类型(`image/*`、`audio/*`、`video/*`)的逗号分隔列表。空值或 `*/*` 允许所有类型。 +- `ALLOWED_TYPES`: **.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: 允许的文件扩展名(`.zip`)、mime 类型(`text/plain`)或通配符类型(`image/*`、`audio/*`、`video/*`)的逗号分隔列表。空值或 `*/*` 允许所有类型。 - `MAX_SIZE`: **2048**: 附件的最大限制(MB)。 - `MAX_FILES`: **5**: 一次最多上传的附件数量。 - `STORAGE_TYPE`: **local**: 附件的存储类型,`local` 表示本地磁盘,`minio` 表示兼容 S3 的对象存储服务,如果未设置将使用默认值 `local` 或其他在 `[storage.xxx]` 中定义的名称。 diff --git a/modules/setting/attachment.go b/modules/setting/attachment.go index 934d4d7f46..0fdabb5032 100644 --- a/modules/setting/attachment.go +++ b/modules/setting/attachment.go @@ -12,7 +12,7 @@ var Attachment = struct { Enabled bool }{ Storage: &Storage{}, - AllowedTypes: ".csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip", + AllowedTypes: ".cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip", MaxSize: 2048, MaxFiles: 5, Enabled: true, @@ -25,7 +25,7 @@ func loadAttachmentFrom(rootCfg ConfigProvider) (err error) { return err } - Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip") + Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip") Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(2048) Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5) Attachment.Enabled = sec.Key("ENABLED").MustBool(true) From 1b48084e59e9ab92bf8af142c7c8ebe7b0367267 Mon Sep 17 00:00:00 2001 From: Chongyi Zheng <git@zcy.dev> Date: Sat, 9 Mar 2024 08:13:08 -0500 Subject: [PATCH 56/62] Fix action runner offline label padding (#29691) Before: The `offline` padding is `calc(.833em - 1px)` from `basic` CSS class, but `idle` padding is `6px`. <img width="1035" alt="image" src="https://github.com/go-gitea/gitea/assets/37034805/ccb42615-20d7-4032-a805-40cd9643012d"> After: <img width="1035" alt="image" src="https://github.com/go-gitea/gitea/assets/37034805/d6af99c8-76cb-4850-96d6-5289b06e1ca8"> (cherry picked from commit a192a5ed99c2a244d0f015d62088642eb5a81d75) --- templates/shared/actions/runner_list.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/shared/actions/runner_list.tmpl b/templates/shared/actions/runner_list.tmpl index cf4210ab9e..cce5b60f07 100644 --- a/templates/shared/actions/runner_list.tmpl +++ b/templates/shared/actions/runner_list.tmpl @@ -68,7 +68,7 @@ {{range .Runners}} <tr> <td> - <span class="ui {{if .IsOnline}}green{{else}}basic{{end}} label">{{.StatusLocaleName ctx.Locale}}</span> + <span class="ui {{if .IsOnline}}green{{end}} label">{{.StatusLocaleName ctx.Locale}}</span> </td> <td>{{.ID}}</td> <td><p data-tooltip-content="{{.Description}}">{{.Name}}</p></td> From 27beed74b550959c08f12697917861bec23978cd Mon Sep 17 00:00:00 2001 From: silverwind <me@silverwind.io> Date: Sat, 9 Mar 2024 18:37:29 +0100 Subject: [PATCH 57/62] Suppress error from monaco-editor (#29684) Fixes: https://github.com/go-gitea/gitea/issues/29414 I see no way for us to catch this error, so downgrade it until https://github.com/microsoft/monaco-editor/issues/4325 is fixed, which will likely take a few weeks to propagate up from vscode. The entries in `updates.config.js` will make [`updates`](https://github.com/silverwind/updates) not upgrade these anymore and I think it's good documentation as well to have the reasons why we don't upgrade these dependencies. (cherry picked from commit 9bf693d98ddf8efa058a5fbbb6a3da5e0c12ab27) --- web_src/js/bootstrap.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/web_src/js/bootstrap.js b/web_src/js/bootstrap.js index c0047b0ac2..698d17fa36 100644 --- a/web_src/js/bootstrap.js +++ b/web_src/js/bootstrap.js @@ -6,10 +6,18 @@ // This file must be imported before any lazy-loading is being attempted. __webpack_public_path__ = `${window.config?.assetUrlPrefix ?? '/assets'}/`; +const filteredErrors = new Set([ + 'getModifierState is not a function', // https://github.com/microsoft/monaco-editor/issues/4325 +]); + export function showGlobalErrorMessage(msg) { const pageContent = document.querySelector('.page-content'); if (!pageContent) return; + for (const filteredError of filteredErrors) { + if (msg.includes(filteredError)) return; + } + // compact the message to a data attribute to avoid too many duplicated messages const msgCompact = msg.replace(/\W/g, '').trim(); let msgDiv = pageContent.querySelector(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`); From 1b8aa3ab6ff3a9e99d1f415e3bc9080a41d16a70 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Sun, 10 Mar 2024 09:32:48 +0800 Subject: [PATCH 58/62] Fix broken webhooks (#29690) Fix #29689 (cherry picked from commit 6e8762f962c5eaaee1c92e910c95c8b85b7c1e11) --- services/webhook/payloader.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/webhook/payloader.go b/services/webhook/payloader.go index abf9946cca..54a11a5868 100644 --- a/services/webhook/payloader.go +++ b/services/webhook/payloader.go @@ -94,7 +94,12 @@ func newJSONRequest[T any](pc payloadConvertor[T], w *webhook_model.Webhook, t * return nil, nil, err } - req, err := http.NewRequest(w.HTTPMethod, w.URL, bytes.NewReader(body)) + method := w.HTTPMethod + if method == "" { + method = http.MethodPost + } + + req, err := http.NewRequest(method, w.URL, bytes.NewReader(body)) if err != nil { return nil, nil, err } From c243323b4a97f98612c2ea569c6ea92fce8d0ee7 Mon Sep 17 00:00:00 2001 From: Earl Warren <contact@earl-warren.org> Date: Sun, 10 Mar 2024 17:13:52 +0700 Subject: [PATCH 59/62] [DEADCODE] update --- .deadcode-out | 1 + 1 file changed, 1 insertion(+) diff --git a/.deadcode-out b/.deadcode-out index cf5f7c479b..fd7b3ad858 100644 --- a/.deadcode-out +++ b/.deadcode-out @@ -189,6 +189,7 @@ package "code.gitea.io/gitea/modules/gitgraph" package "code.gitea.io/gitea/modules/gitrepo" func GetBranchCommitID + func GetWikiDefaultBranch package "code.gitea.io/gitea/modules/graceful" func (*Manager).TerminateContext From ed6f724a5de6ee91d90995a865e1d5587ea02e86 Mon Sep 17 00:00:00 2001 From: oliverpool <git@olivier.pfad.fr> Date: Mon, 11 Mar 2024 09:42:58 +0100 Subject: [PATCH 60/62] add test for webhook default method https://github.com/go-gitea/gitea/pull/29690 --- services/webhook/deliver_test.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/services/webhook/deliver_test.go b/services/webhook/deliver_test.go index 3324c7f0e3..bc06e43e03 100644 --- a/services/webhook/deliver_test.go +++ b/services/webhook/deliver_test.go @@ -233,7 +233,8 @@ func TestWebhookDeliverSpecificTypes(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) type hookCase struct { - gotBody chan []byte + gotBody chan []byte + expectedMethod string } cases := map[string]hookCase{ @@ -256,7 +257,8 @@ func TestWebhookDeliverSpecificTypes(t *testing.T) { gotBody: make(chan []byte, 1), }, webhook_module.MATRIX: { - gotBody: make(chan []byte, 1), + gotBody: make(chan []byte, 1), + expectedMethod: "PUT", }, webhook_module.WECHATWORK: { gotBody: make(chan []byte, 1), @@ -271,6 +273,13 @@ func TestWebhookDeliverSpecificTypes(t *testing.T) { typ := strings.Split(r.URL.Path, "/")[1] // take first segment (after skipping leading slash) hc := cases[typ] + + if hc.expectedMethod != "" { + assert.Equal(t, hc.expectedMethod, r.Method, r.URL.Path) + } else { + assert.Equal(t, "POST", r.Method, r.URL.Path) + } + require.NotNil(t, hc.gotBody, r.URL.Path) body, err := io.ReadAll(r.Body) assert.NoError(t, err) @@ -293,8 +302,8 @@ func TestWebhookDeliverSpecificTypes(t *testing.T) { IsActive: true, Type: typ, URL: s.URL + "/" + typ, - HTTPMethod: "POST", - ContentType: 0, // set to 0 so that falling back to default request fails with "invalid content type" + HTTPMethod: "", // should fallback to POST, when left unset by the specific hook + ContentType: 0, // set to 0 so that falling back to default request fails with "invalid content type" Meta: "{}", } assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook)) From 12e317c198f698c58e88d8ac7e84791eb23a935a Mon Sep 17 00:00:00 2001 From: oliverpool <git@olivier.pfad.fr> Date: Mon, 11 Mar 2024 09:50:30 +0100 Subject: [PATCH 61/62] add test for webhook migration --- .../hook_task.yml | 16 +++++ .../hook_task_migrated.yml | 18 ++++++ models/migrations/v1_22/v290.go | 22 +++++++ models/migrations/v1_22/v290_test.go | 58 +++++++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml create mode 100644 models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml create mode 100644 models/migrations/v1_22/v290_test.go diff --git a/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml b/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml new file mode 100644 index 0000000000..716a2a017d --- /dev/null +++ b/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml @@ -0,0 +1,16 @@ +- id: 11 + uuid: uuid11 + hook_id: 1 + payload_content: > + {"data":"payload"} + event_type: create + delivered: 1706106005 + +- id: 101 + uuid: uuid101 + hook_id: 1 + payload_content: > + {"data":"payload"} + event_type: create + delivered: 1706106006 + is_delivered: true diff --git a/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml b/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml new file mode 100644 index 0000000000..913d927d91 --- /dev/null +++ b/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml @@ -0,0 +1,18 @@ +- id: 11 + uuid: uuid11 + hook_id: 1 + payload_content: > + {"data":"payload"} + event_type: create + delivered: 1706106005 + payload_version: 1 + +- id: 101 + uuid: uuid101 + hook_id: 1 + payload_content: > + {"data":"payload"} + event_type: create + delivered: 1706106006 + is_delivered: true + payload_version: 1 diff --git a/models/migrations/v1_22/v290.go b/models/migrations/v1_22/v290.go index e9b7f504ba..e9c471b3dd 100644 --- a/models/migrations/v1_22/v290.go +++ b/models/migrations/v1_22/v290.go @@ -4,10 +4,32 @@ package v1_22 //nolint import ( + "code.gitea.io/gitea/modules/timeutil" + webhook_module "code.gitea.io/gitea/modules/webhook" + "xorm.io/xorm" ) +// HookTask represents a hook task. +// exact copy of models/webhook/hooktask.go when this migration was created +// - xorm:"-" fields deleted type HookTask struct { + ID int64 `xorm:"pk autoincr"` + HookID int64 `xorm:"index"` + UUID string `xorm:"unique"` + PayloadContent string `xorm:"LONGTEXT"` + EventType webhook_module.HookEventType + IsDelivered bool + Delivered timeutil.TimeStampNano + + // History info. + IsSucceed bool + RequestContent string `xorm:"LONGTEXT"` + ResponseContent string `xorm:"LONGTEXT"` + + // Version number to allow for smooth version upgrades: + // - Version 1: PayloadContent contains the JSON as send to the URL + // - Version 2: PayloadContent contains the original event PayloadVersion int `xorm:"DEFAULT 1"` } diff --git a/models/migrations/v1_22/v290_test.go b/models/migrations/v1_22/v290_test.go new file mode 100644 index 0000000000..24a1c0b0a5 --- /dev/null +++ b/models/migrations/v1_22/v290_test.go @@ -0,0 +1,58 @@ +// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "strconv" + "testing" + + "code.gitea.io/gitea/models/migrations/base" + "code.gitea.io/gitea/modules/timeutil" + webhook_module "code.gitea.io/gitea/modules/webhook" + + "github.com/stretchr/testify/assert" +) + +func Test_AddPayloadVersionToHookTaskTable(t *testing.T) { + type HookTaskMigrated HookTask + + // HookTask represents a hook task, as of before the migration + type HookTask struct { + ID int64 `xorm:"pk autoincr"` + HookID int64 `xorm:"index"` + UUID string `xorm:"unique"` + PayloadContent string `xorm:"LONGTEXT"` + EventType webhook_module.HookEventType + IsDelivered bool + Delivered timeutil.TimeStampNano + + // History info. + IsSucceed bool + RequestContent string `xorm:"LONGTEXT"` + ResponseContent string `xorm:"LONGTEXT"` + } + + // Prepare and load the testing database + x, deferable := base.PrepareTestEnv(t, 0, new(HookTask), new(HookTaskMigrated)) + defer deferable() + if x == nil || t.Failed() { + return + } + + assert.NoError(t, AddPayloadVersionToHookTaskTable(x)) + + expected := []HookTaskMigrated{} + assert.NoError(t, x.Table("hook_task_migrated").Asc("id").Find(&expected)) + assert.Len(t, expected, 2) + + got := []HookTaskMigrated{} + assert.NoError(t, x.Table("hook_task").Asc("id").Find(&got)) + + for i, expected := range expected { + expected, got := expected, got[i] + t.Run(strconv.FormatInt(expected.ID, 10), func(t *testing.T) { + assert.Equal(t, expected.PayloadVersion, got.PayloadVersion) + }) + } +} From 63973e2c70fb636c49dc2e66b621ef679f903e94 Mon Sep 17 00:00:00 2001 From: oliverpool <git@olivier.pfad.fr> Date: Mon, 11 Mar 2024 09:50:46 +0100 Subject: [PATCH 62/62] add repo_settings_hook_test --- tests/integration/repo_settings_hook_test.go | 62 ++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/integration/repo_settings_hook_test.go diff --git a/tests/integration/repo_settings_hook_test.go b/tests/integration/repo_settings_hook_test.go new file mode 100644 index 0000000000..8b800c4d6b --- /dev/null +++ b/tests/integration/repo_settings_hook_test.go @@ -0,0 +1,62 @@ +// Copyright 2022 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "net/http" + "strings" + "testing" + + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestRepoSettingsHookHistory(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + // Request repository hook page with history + req := NewRequest(t, "GET", "/user2/repo1/settings/hooks/1") + resp := session.MakeRequest(t, req, http.StatusOK) + + doc := NewHTMLParser(t, resp.Body) + + t.Run("1/delivered", func(t *testing.T) { + html, err := doc.doc.Find(".webhook div[data-tab='request-1']").Html() + assert.NoError(t, err) + assert.Contains(t, html, "<strong>Request URL:</strong> /matrix-delivered\n") + assert.Contains(t, html, "<strong>Request method:</strong> PUT") + assert.Contains(t, html, "<strong>X-Head:</strong> 42") + assert.Contains(t, html, `<code class="json">{}</code>`) + + val, ok := doc.doc.Find(".webhook div.item:has(div#info-1) svg").Attr("class") + assert.True(t, ok) + assert.Equal(t, "svg octicon-alert", val) + }) + + t.Run("2/undelivered", func(t *testing.T) { + html, err := doc.doc.Find(".webhook div[data-tab='request-2']").Html() + assert.NoError(t, err) + assert.Equal(t, "-", strings.TrimSpace(html)) + + val, ok := doc.doc.Find(".webhook div.item:has(div#info-2) svg").Attr("class") + assert.True(t, ok) + assert.Equal(t, "svg octicon-stopwatch", val) + }) + + t.Run("3/success", func(t *testing.T) { + html, err := doc.doc.Find(".webhook div[data-tab='request-3']").Html() + assert.NoError(t, err) + assert.Contains(t, html, "<strong>Request URL:</strong> /matrix-success\n") + assert.Contains(t, html, "<strong>Request method:</strong> PUT") + assert.Contains(t, html, "<strong>X-Head:</strong> 42") + assert.Contains(t, html, `<code class="json">{"key":"value"}</code>`) + + val, ok := doc.doc.Find(".webhook div.item:has(div#info-3) svg").Attr("class") + assert.True(t, ok) + assert.Equal(t, "svg octicon-check", val) + }) +}