diff --git a/docs/content/doc/developers/api-usage.en-us.md b/docs/content/doc/developers/api-usage.en-us.md index dd2822e9f136..1ff912353fed 100644 --- a/docs/content/doc/developers/api-usage.en-us.md +++ b/docs/content/doc/developers/api-usage.en-us.md @@ -105,6 +105,18 @@ curl -X POST "http://localhost:4000/api/v1/repos/test1/test1/issues" \ As mentioned above, the token used is the same one you would use in the `token=` string in a GET request. +## Pagination + +The API supports pagination. The `page` and `limit` parameters are used to specify the page number and the number of items per page. As well, the `Link` header is returned with the next, previous, and last page links if there are more than one pages. The `x-total-count` is also returned to indicate the total number of items. + +```sh +curl -v "http://localhost/api/v1/repos/search?limit=1" +... +< link: ; rel="next",; rel="last" +... +< x-total-count: 5252 +``` + ## API Guide: API Reference guide is auto-generated by swagger and available on: diff --git a/docs/content/doc/packages/nuget.en-us.md b/docs/content/doc/packages/nuget.en-us.md index a4435fa99f01..6c8aaa70af1d 100644 --- a/docs/content/doc/packages/nuget.en-us.md +++ b/docs/content/doc/packages/nuget.en-us.md @@ -47,6 +47,8 @@ For example: dotnet nuget add source --name gitea --username testuser --password password123 https://gitea.example.com/api/packages/testuser/nuget/index.json ``` +You can add the source without credentials and use the [`--api-key`](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-nuget-push) parameter when publishing packages. In this case you need to provide a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}). + ## Publish a package Publish a package by running the following command: diff --git a/integrations/api_packages_container_test.go b/integrations/api_packages_container_test.go index 5e073f313f7a..1c4f9e74755b 100644 --- a/integrations/api_packages_container_test.go +++ b/integrations/api_packages_container_test.go @@ -276,11 +276,23 @@ func TestPackageContainer(t *testing.T) { } } - // Overwrite existing tag + req = NewRequest(t, "GET", fmt.Sprintf("%s/manifests/%s", url, tag)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusOK) + + pv, err = packages_model.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, tag) + assert.NoError(t, err) + assert.EqualValues(t, 1, pv.DownloadCount) + + // Overwrite existing tag should keep the download count req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, tag), strings.NewReader(manifestContent)) addTokenAuthHeader(req, userToken) req.Header.Set("Content-Type", oci.MediaTypeDockerManifest) MakeRequest(t, req, http.StatusCreated) + + pv, err = packages_model.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, tag) + assert.NoError(t, err) + assert.EqualValues(t, 1, pv.DownloadCount) }) t.Run("HeadManifest", func(t *testing.T) { diff --git a/integrations/api_packages_nuget_test.go b/integrations/api_packages_nuget_test.go index 346f391f82fc..06eb485541ef 100644 --- a/integrations/api_packages_nuget_test.go +++ b/integrations/api_packages_nuget_test.go @@ -24,9 +24,16 @@ import ( "github.com/stretchr/testify/assert" ) +func addNuGetAPIKeyHeader(request *http.Request, token string) *http.Request { + request.Header.Set("X-NuGet-ApiKey", token) + return request +} + func TestPackageNuGet(t *testing.T) { defer prepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) + token := getUserToken(t, user.Name) packageName := "test.package" packageVersion := "1.0.3" @@ -60,6 +67,10 @@ func TestPackageNuGet(t *testing.T) { req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url)) req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url)) + req = addNuGetAPIKeyHeader(req, token) resp := MakeRequest(t, req, http.StatusOK) var result nuget.ServiceIndexResponse diff --git a/integrations/pull_status_test.go b/integrations/pull_status_test.go index 33a27cd81275..d38d90169bff 100644 --- a/integrations/pull_status_test.go +++ b/integrations/pull_status_test.go @@ -56,11 +56,11 @@ func TestPullCreate_CommitStatus(t *testing.T) { } statesIcons := map[api.CommitStatusState]string{ - api.CommitStatusPending: "circle icon yellow", - api.CommitStatusSuccess: "check icon green", - api.CommitStatusError: "warning icon red", - api.CommitStatusFailure: "remove icon red", - api.CommitStatusWarning: "warning sign icon yellow", + api.CommitStatusPending: "octicon-dot-fill", + api.CommitStatusSuccess: "octicon-check", + api.CommitStatusError: "gitea-exclamation", + api.CommitStatusFailure: "octicon-x", + api.CommitStatusWarning: "gitea-exclamation", } testCtx := NewAPITestContext(t, "user1", "repo1") @@ -80,9 +80,9 @@ func TestPullCreate_CommitStatus(t *testing.T) { assert.NotEmpty(t, commitURL) assert.EqualValues(t, commitID, path.Base(commitURL)) - cls, ok := doc.doc.Find("#commits-table tbody tr td.message i.commit-status").Last().Attr("class") + cls, ok := doc.doc.Find("#commits-table tbody tr td.message .commit-status").Last().Attr("class") assert.True(t, ok) - assert.EqualValues(t, "commit-status "+statesIcons[status], cls) + assert.Contains(t, cls, statesIcons[status]) } }) } diff --git a/integrations/repo_commits_test.go b/integrations/repo_commits_test.go index 7107f43b0fed..b18b35da1e65 100644 --- a/integrations/repo_commits_test.go +++ b/integrations/repo_commits_test.go @@ -55,7 +55,7 @@ func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) { doc = NewHTMLParser(t, resp.Body) // Check if commit status is displayed in message column - sel := doc.doc.Find("#commits-table tbody tr td.message a.commit-statuses-trigger i.commit-status") + sel := doc.doc.Find("#commits-table tbody tr td.message a.commit-statuses-trigger .commit-status") assert.Equal(t, 1, sel.Length()) for _, class := range classes { assert.True(t, sel.HasClass(class)) @@ -96,21 +96,21 @@ func testRepoCommitsWithStatus(t *testing.T, resp, respOne *httptest.ResponseRec } func TestRepoCommitsWithStatusPending(t *testing.T) { - doTestRepoCommitWithStatus(t, "pending", "circle", "yellow") + doTestRepoCommitWithStatus(t, "pending", "octicon-dot-fill", "yellow") } func TestRepoCommitsWithStatusSuccess(t *testing.T) { - doTestRepoCommitWithStatus(t, "success", "check", "green") + doTestRepoCommitWithStatus(t, "success", "octicon-check", "green") } func TestRepoCommitsWithStatusError(t *testing.T) { - doTestRepoCommitWithStatus(t, "error", "warning", "red") + doTestRepoCommitWithStatus(t, "error", "gitea-exclamation", "red") } func TestRepoCommitsWithStatusFailure(t *testing.T) { - doTestRepoCommitWithStatus(t, "failure", "remove", "red") + doTestRepoCommitWithStatus(t, "failure", "octicon-x", "red") } func TestRepoCommitsWithStatusWarning(t *testing.T) { - doTestRepoCommitWithStatus(t, "warning", "warning", "sign", "yellow") + doTestRepoCommitWithStatus(t, "warning", "gitea-exclamation", "yellow") } diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 6e7e64d2a3ee..068891b7794e 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -1063,6 +1063,7 @@ normal_view=Vista normale line=riga lines=righe +editor.add_file=Aggiungi file editor.new_file=Nuovo file editor.upload_file=Carica File editor.edit_file=Modifica File @@ -1268,6 +1269,8 @@ issues.filter_milestone=Traguardo issues.filter_milestone_no_select=Tutte le pietre miliari issues.filter_assignee=Assegnatario issues.filter_assginee_no_select=Tutte le assegnazioni +issues.filter_poster=Autore +issues.filter_poster_no_select=Tutti gli autori issues.filter_type=Tipo issues.filter_type.all_issues=Tutti i problemi issues.filter_type.assigned_to_you=Assegnati a te @@ -3111,6 +3114,9 @@ npm.dependencies.development=Dipendenze Di Sviluppo npm.dependencies.peer=Dipendenze Peer npm.dependencies.optional=Dipendenze Opzionali npm.details.tag=Tag +pub.install=Per installare il pacchetto utilizzando NuGet, eseguire il seguente comando: +pub.documentation=Per ulteriori informazioni sul registro Pub, consultare la documentazione. +pub.details.repository_site=Sito Repository pypi.requires=Richiede Python pypi.install=Per installare il pacchetto usando pip, eseguire il seguente comando: pypi.documentation=Per ulteriori informazioni sul registro PyPI, consultare la documentazione. diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 99857b96e12d..e5b8ffefcd7b 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -1063,6 +1063,7 @@ normal_view=Normale weergave line=regel lines=regels +editor.add_file=Bestand toevoegen editor.new_file=Nieuw bestand editor.upload_file=Upload bestand editor.edit_file=Bewerk bestand @@ -1268,6 +1269,8 @@ issues.filter_milestone=Mijlpaal issues.filter_milestone_no_select=Alle mijlpalen issues.filter_assignee=Aangewezene issues.filter_assginee_no_select=Alle toegewezen personen +issues.filter_poster=Auteur +issues.filter_poster_no_select=Alle auteurs issues.filter_type=Type issues.filter_type.all_issues=Alle kwesties issues.filter_type.assigned_to_you=Aan jou toegewezen @@ -1296,6 +1299,11 @@ issues.action_milestone_no_select=Geen mijlpaal issues.action_assignee=Toegewezene issues.action_assignee_no_select=Geen verantwoordelijke issues.opened_by=%[1]s geopend door %[3]s +pulls.merged_by=door %[3]s was samengevoegd %[1]s +pulls.merged_by_fake=bij %[2]s is %[1]s samengevoegd +issues.closed_by=door %[3]s was gesloten %[1]s +issues.opened_by_fake=%[1]s geopend door %[2]s +issues.closed_by_fake=door %[2]s was gesloten %[1]s issues.previous=Vorige issues.next=Volgende issues.open_title=Open @@ -1311,6 +1319,8 @@ issues.context.edit=Bewerken issues.context.delete=Verwijder issues.no_content=Er is nog geen inhoud. issues.close_issue=Sluit +issues.pull_merged_at=`commit samengevoegd %[2]s in %[3]s %[4]s` +issues.manually_pull_merged_at=`commit handmatig samengevoegd %[2]s in %[3]s %[4]s` issues.close_comment_issue=Reageer en sluit issues.reopen_issue=Heropen issues.reopen_comment_issue=Heropen en geef commentaar @@ -1333,6 +1343,7 @@ issues.is_stale=Er zijn wijzigingen aangebracht in deze PR sinds deze beoordelin issues.remove_request_review=Verwijder beoordelingsverzoek issues.remove_request_review_block=Kan beoordelingsverzoek niet verwijderen issues.dismiss_review=Beoordeling afwijzen +issues.dismiss_review_warning=Bent u zeker dat u deze beoordeling wilt afwijzen? issues.sign_in_require_desc=Log in om deel te nemen aan deze discussie. issues.edit=Bewerken issues.cancel=Annuleren @@ -1406,6 +1417,7 @@ issues.error_modifying_due_date=Deadline aanpassen mislukt. issues.error_removing_due_date=Deadline verwijderen mislukt. issues.push_commit_1=toegevoegd %d commit %s issues.push_commits_n=toegevoegd %d commits %s +issues.force_push_codes=`force-push %[1]s van %[2]s naar %[4]s %[6]s` issues.due_date_form=jjjj-mm-dd issues.due_date_form_add=Vervaldatum toevoegen issues.due_date_form_edit=Bewerk @@ -1426,6 +1438,7 @@ issues.dependency.remove=Verwijder issues.dependency.remove_info=Verwijder afhankelijkheid issues.dependency.added_dependency=`voegde een nieuwe afhankelijkheid %s toe ` issues.dependency.removed_dependency=`verwijderde een afhankelijkheid %s` +issues.dependency.pr_closing_blockedby=Het sluiten van deze pull-aanvraag is geblokkeerd door de volgende issues issues.dependency.issue_closing_blockedby=Het sluiten van dit issue is geblokkeerd door de volgende problemen issues.dependency.issue_close_blocks=Deze kwestie blokkeert het sluiten van de volgende kwesties issues.dependency.pr_close_blocks=Deze pull-aanvraag blokkeert het sluiten van de volgende kwesties @@ -1598,6 +1611,8 @@ pulls.auto_merge_newly_scheduled=De pull-verzoek was gepland om samen te voegen pulls.auto_merge_has_pending_schedule=%[1]s heeft deze pull-verzoek automatisch samengevoegd wanneer alle checks succesvol zijn geweest %[2]s. pulls.auto_merge_cancel_schedule=Automatisch samenvoegen annuleren +pulls.auto_merge_not_scheduled=Deze pull-aanvraag is niet gepland om automatisch samen te voegen. +pulls.auto_merge_canceled_schedule=De automatisch samenvoegen is geannuleerd voor deze pull-aanvraag. pulls.delete.title=Deze pull-verzoek verwijderen? @@ -1761,6 +1776,7 @@ settings.hooks=Webhooks settings.githooks=Git-hooks settings.basic_settings=Basis instellingen settings.mirror_settings=Kopie Settings +settings.mirror_settings.mirrored_repository=Gespiegelde repository settings.mirror_settings.direction=Richting settings.mirror_settings.direction.pull=Pull settings.mirror_settings.direction.push=Push @@ -1790,6 +1806,8 @@ settings.tracker_url_format_error=Het URL-formaat van de externe wiki is geen ge settings.tracker_issue_style=Nummerformaat van de externe kwestie-tracker settings.tracker_issue_style.numeric=Nummeriek settings.tracker_issue_style.alphanumeric=Alfanummeriek +settings.tracker_issue_style.regexp=Reguliere expressie +settings.tracker_issue_style.regexp_pattern=Reguliere expressie patroon settings.tracker_url_format_desc=Gebruik de aanduidingen {user}, {repo} en {index} voor de gebruikersnaam, repositorynaam en kwestie-index. settings.enable_timetracker=Tijdregistratie inschakelen settings.allow_only_contributors_to_track_time=Sta alleen bijdragers toe tijdregistratie te gebruiken diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index b0bb9e1b16cc..a0446debee74 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1269,6 +1269,8 @@ issues.filter_milestone=Etapa issues.filter_milestone_no_select=Todas as etapas issues.filter_assignee=Encarregado issues.filter_assginee_no_select=Todos os encarregados +issues.filter_poster=Autor(a) +issues.filter_poster_no_select=Todos os autores issues.filter_type=Tipo issues.filter_type.all_issues=Todas as questões issues.filter_type.assigned_to_you=Atribuídas a si diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index d33713c684f3..c75a4f589a5c 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -1269,6 +1269,8 @@ issues.filter_milestone=Kilometre Taşı issues.filter_milestone_no_select=Tüm kilometre taşları issues.filter_assignee=Atanan issues.filter_assginee_no_select=Tüm atananlar +issues.filter_poster=Yazar +issues.filter_poster_no_select=Tüm yazarlar issues.filter_type=Tür issues.filter_type.all_issues=Tüm konular issues.filter_type.assigned_to_you=Size atanan diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 5d8e427a9125..fc9b8ffc8a69 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -179,6 +179,8 @@ log_root_path_helper=日志文件将写入此目录。 optional_title=可选设置 email_title=电子邮箱设置 +smtp_addr=SMTP 主机地址 +smtp_port=SMTP 端口 smtp_from=电子邮件发件人 smtp_from_helper=电子邮件地址 Gitea 将使用。输入一个普通的电子邮件地址或使用 "名称" 格式。 mailer_user=SMTP 用户名 @@ -798,6 +800,7 @@ email_notifications.enable=启用邮件通知 email_notifications.onmention=只在被提到时邮件通知 email_notifications.disable=停用邮件通知 email_notifications.submit=邮件通知设置 +email_notifications.andyourown=和您自己的通知 visibility=用户可见性 visibility.public=公开 @@ -931,6 +934,7 @@ form.name_pattern_not_allowed=仓库名称中不允许使用模式 "%s"。 need_auth=授权 migrate_options=迁移选项 migrate_service=迁移服务 +migrate_options_mirror_helper=该仓库将是一个镜像 migrate_options_lfs=迁移 LFS 文件 migrate_options_lfs_endpoint.label=LFS 网址 migrate_options_lfs_endpoint.description=迁移将尝试使用你的 Git remote 来 确定 LFS 服务器。如果仓库 LFS 数据存储在其他位置,你还可以指定自定义网址。 @@ -1059,6 +1063,7 @@ normal_view=普通视图 line=行 lines=行 +editor.add_file=添加文件 editor.new_file=新建文件 editor.upload_file=上传文件 editor.edit_file=编辑文件 @@ -1264,6 +1269,8 @@ issues.filter_milestone=里程碑筛选 issues.filter_milestone_no_select=所有里程碑 issues.filter_assignee=指派人筛选 issues.filter_assginee_no_select=所有指派成员 +issues.filter_poster=作者 +issues.filter_poster_no_select=所有作者 issues.filter_type=类型筛选 issues.filter_type.all_issues=所有工单 issues.filter_type.assigned_to_you=指派给您的 @@ -1418,6 +1425,7 @@ issues.due_date_form_remove=删除 issues.due_date_not_writer=你需要仓库写入权限来修改工单到期时间。 issues.due_date_not_set=未设置到期时间。 issues.due_date_added=于 %[2]s 设置到期时间为 %[1]s +issues.due_date_modified=将到期日从 %[2]s 修改为 %[1]s %[3]s issues.due_date_remove=于 %[2]s 删除了到期时间 %[1]s issues.due_date_overdue=过期 issues.due_date_invalid=到期日期无效或超出范围。请使用 'yyyy-mm-dd' 格式。 @@ -1529,6 +1537,8 @@ pulls.remove_prefix=删除 %s 前缀 pulls.data_broken=此合并请求因为派生仓库信息缺失而中断。 pulls.files_conflicted=此合并请求有变更与目标分支冲突。 pulls.is_checking=正在进行合并冲突检测,请稍后再试。 +pulls.is_ancestor=此分支已经包含在目标分支中,没有什么可以合并。 +pulls.is_empty=此分支上的更改已经在目标分支上。这将是一个空提交。 pulls.required_status_check_failed=一些必要的检查没有成功 pulls.required_status_check_missing=缺少一些必要的检查。 pulls.required_status_check_administrator=作为管理员,您仍可合并此合并请求 @@ -2531,6 +2541,8 @@ users.delete_account=删除帐户 users.cannot_delete_self=你不能删除自己 users.still_own_repo=此用户仍然拥有一个或多个仓库。必须首先删除或转让这些仓库。 users.still_has_org=此用户是组织的成员。必须先从组织中删除用户。 +users.purge=清理用户 +users.purge_help=强制删除用户和用户拥有的任何仓库、组织和软件包。所有评论也将被删除。 users.still_own_packages=此用户仍然拥有一个或多个软件包。请先删除这些软件包。 users.deletion_success=用户帐户已被删除。 users.reset_2fa=重置两步验证 @@ -2787,13 +2799,19 @@ config.queue_length=队列长度 config.deliver_timeout=推送超时 config.skip_tls_verify=跳过 TLS 验证 +config.mailer_config=Mailer 配置 config.mailer_enabled=启用服务 +config.mailer_enable_helo=启用HELO config.mailer_name=任务名称 +config.mailer_protocol=协议 +config.mailer_smtp_addr=SMTP 地址 +config.mailer_smtp_port=SMTP 端口 config.mailer_user=发送者帐号 config.mailer_use_sendmail=使用 Sendmail config.mailer_sendmail_path=Sendmail 路径 config.mailer_sendmail_args=Sendmail 的额外参数 config.mailer_sendmail_timeout=Sendmail 超时 +config.mailer_use_dummy=Dummy config.test_email_placeholder=电子邮址 (例如,test@example.com) config.send_test_mail=发送测试邮件 config.test_mail_failed=发送测试邮件至 '%s' 时失败:%v @@ -3030,6 +3048,7 @@ title=软件包 desc=管理仓库软件包。 empty=还没有软件包。 empty.documentation=关于软件包注册中心的更多信息,请参阅 文档 。 +empty.repo=您上传了一个包,但没有显示在这里吗?转到 包设置 并将其链接到这个仓库中。 filter.type=类型 filter.type.all=所有 filter.no_result=您的过滤器没有产生任何结果。 @@ -3095,6 +3114,10 @@ npm.dependencies.development=开发依赖 npm.dependencies.peer=Peer 依赖 npm.dependencies.optional=可选依赖 npm.details.tag=标签 +pub.install=要使用 Dart 安装软件包,请运行以下命令: +pub.documentation=关于 Pub 注册中心的信息,请参阅 文档。 +pub.details.repository_site=仓库站点 +pub.details.documentation_site=文档站点 pypi.requires=需要 Python pypi.install=要使用 pip 安装软件包,请运行以下命令: pypi.documentation=关于 PyPI 注册中心的信息,请参阅 文档。 diff --git a/public/img/svg/gitea-exclamation.svg b/public/img/svg/gitea-exclamation.svg new file mode 100644 index 000000000000..22176010dc9f --- /dev/null +++ b/public/img/svg/gitea-exclamation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 39ba41cdfbf1..cbf041a7e136 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -46,6 +46,7 @@ func Routes() *web.Route { authMethods := []auth.Method{ &auth.OAuth2{}, &auth.Basic{}, + &nuget.Auth{}, &conan.Auth{}, } if setting.Service.EnableReverseProxyAuth { diff --git a/routers/api/packages/container/manifest.go b/routers/api/packages/container/manifest.go index 319c9bcabc11..8beed3dbb729 100644 --- a/routers/api/packages/container/manifest.go +++ b/routers/api/packages/container/manifest.go @@ -312,6 +312,9 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met return nil, err } + // keep download count on overwrite + _pv.DownloadCount = pv.DownloadCount + if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil { log.Error("Error inserting package: %v", err) return nil, err diff --git a/routers/api/packages/nuget/auth.go b/routers/api/packages/nuget/auth.go new file mode 100644 index 000000000000..26a5b9018931 --- /dev/null +++ b/routers/api/packages/nuget/auth.go @@ -0,0 +1,45 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package nuget + +import ( + "net/http" + + "code.gitea.io/gitea/models" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/services/auth" +) + +type Auth struct{} + +func (a *Auth) Name() string { + return "nuget" +} + +// https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#request-parameters +func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) *user_model.User { + token, err := models.GetAccessTokenBySHA(req.Header.Get("X-NuGet-ApiKey")) + if err != nil { + if !(models.IsErrAccessTokenNotExist(err) || models.IsErrAccessTokenEmpty(err)) { + log.Error("GetAccessTokenBySHA: %v", err) + } + return nil + } + + u, err := user_model.GetUserByID(token.UID) + if err != nil { + log.Error("GetUserByID: %v", err) + return nil + } + + token.UpdatedUnix = timeutil.TimeStampNow() + if err := models.UpdateAccessToken(token); err != nil { + log.Error("UpdateAccessToken: %v", err) + } + + return u +} diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 648269980472..f338c525b4d3 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -618,6 +618,12 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { shownIssues += int(issueCountByRepo[repoID]) } } + if len(repoIDs) == 1 { + repo := showReposMap[repoIDs[0]] + if repo != nil { + ctx.Data["SingleRepoLink"] = repo.Link() + } + } ctx.Data["IsShowClosed"] = isShowClosed diff --git a/services/repository/template.go b/services/repository/template.go index 3f2291ad6378..b73abdce587f 100644 --- a/services/repository/template.go +++ b/services/repository/template.go @@ -7,12 +7,10 @@ package repository import ( "context" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" repo_module "code.gitea.io/gitea/modules/repository" ) @@ -100,11 +98,6 @@ func GenerateRepository(doer, owner *user_model.User, templateRepo *repo_model.R return nil }); err != nil { - if generateRepo != nil && generateRepo.ID > 0 { - if errDelete := models.DeleteRepository(doer, owner.ID, generateRepo.ID); errDelete != nil { - log.Error("Rollback deleteRepository: %v", errDelete) - } - } return nil, err } diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index 17cbef9f108a..dd2646b50a17 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -117,7 +117,7 @@
-
+
diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index 8dc0083b76e3..5521a28a124e 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -86,10 +86,10 @@ {{.locale.Tr "active_stopwatch"}} -
{{if .IsViewFile}} diff --git a/templates/repo/issue/view_content/add_reaction.tmpl b/templates/repo/issue/view_content/add_reaction.tmpl index 9f4fb21cefcd..1d0ddb62a9cb 100644 --- a/templates/repo/issue/view_content/add_reaction.tmpl +++ b/templates/repo/issue/view_content/add_reaction.tmpl @@ -7,7 +7,7 @@
{{ .ctx.locale.Tr "repo.pick_reaction"}}
{{range $value := AllowedReactions}} -
{{ReactionToEmoji $value}}
+
{{ReactionToEmoji $value}}
{{end}}
diff --git a/templates/repo/settings/deploy_keys.tmpl b/templates/repo/settings/deploy_keys.tmpl index 0a7e614ca812..2f8a4c6c1e25 100644 --- a/templates/repo/settings/deploy_keys.tmpl +++ b/templates/repo/settings/deploy_keys.tmpl @@ -56,7 +56,7 @@
- {{svg "octicon-key" 32}} + {{svg "octicon-key" 32}}
{{.Name}} diff --git a/templates/repo/settings/webhook/history.tmpl b/templates/repo/settings/webhook/history.tmpl index d3d84c1439a7..e99ff6a0b9ca 100644 --- a/templates/repo/settings/webhook/history.tmpl +++ b/templates/repo/settings/webhook/history.tmpl @@ -44,7 +44,7 @@ {{end}} diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl index 3ba8350dcbcf..a25fe28c97f7 100644 --- a/templates/repo/view_file.tmpl +++ b/templates/repo/view_file.tmpl @@ -120,22 +120,12 @@ {{end}} -
+ {{if .SingleRepoLink}} + {{.locale.Tr "repo.issues.new"}} + {{end}} {{template "shared/issuelist" mergeinto . "listType" "dashboard"}} diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index c9f9fe8c254e..063358f0c73b 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -19,7 +19,7 @@ {{$.locale.Tr "settings.delete_token"}} - +
{{.Name}}
diff --git a/templates/user/settings/keys_principal.tmpl b/templates/user/settings/keys_principal.tmpl index a7fab1ea4f14..0ebc46c4ad56 100644 --- a/templates/user/settings/keys_principal.tmpl +++ b/templates/user/settings/keys_principal.tmpl @@ -21,7 +21,7 @@ {{$.locale.Tr "settings.delete_key"}}
- +
{{.Name}}
diff --git a/templates/user/settings/keys_ssh.tmpl b/templates/user/settings/keys_ssh.tmpl index 46254c0dcbb7..7e0158a5f174 100644 --- a/templates/user/settings/keys_ssh.tmpl +++ b/templates/user/settings/keys_ssh.tmpl @@ -47,7 +47,7 @@
- {{svg "octicon-key" 32}} + {{svg "octicon-key" 32}}
{{if .Verified}} diff --git a/web_src/fomantic/build/semantic.css b/web_src/fomantic/build/semantic.css index e60d06b1ffd5..6ea20c3a8ca4 100644 --- a/web_src/fomantic/build/semantic.css +++ b/web_src/fomantic/build/semantic.css @@ -34446,427 +34446,6 @@ Floated Menu / Item /******************************* Site Overrides *******************************/ -/*! - * # Fomantic-UI - Popup - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/******************************* - Popup -*******************************/ - -.ui.popup { - display: none; - position: absolute; - top: 0; - right: 0; - /* Fixes content being squished when inline (moz only) */ - min-width: -webkit-min-content; - min-width: -moz-min-content; - min-width: min-content; - z-index: 1900; - border: 1px solid #D4D4D5; - line-height: 1.4285em; - max-width: 250px; - background: #FFFFFF; - padding: 0.833em 1em; - font-weight: normal; - font-style: normal; - color: rgba(0, 0, 0, 0.87); - border-radius: 0.28571429rem; - box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15); -} - -.ui.popup > .header { - padding: 0; - font-family: var(--fonts-regular); - font-size: 1.14285714em; - line-height: 1.2; - font-weight: 500; -} - -.ui.popup > .header + .content { - padding-top: 0.5em; -} - -.ui.popup:before { - position: absolute; - content: ''; - width: 0.71428571em; - height: 0.71428571em; - background: #FFFFFF; - transform: rotate(45deg); - z-index: 1901; - box-shadow: 1px 1px 0 0 #bababc; -} - -/******************************* - Types -*******************************/ - -/*-------------- - Spacing ----------------*/ - -.ui.popup { - margin: 0; -} - -/* Extending from Top */ - -.ui.top.popup { - margin: 0 0 0.71428571em; -} - -.ui.top.left.popup { - transform-origin: left bottom; -} - -.ui.top.center.popup { - transform-origin: center bottom; -} - -.ui.top.right.popup { - transform-origin: right bottom; -} - -/* Extending from Vertical Center */ - -.ui.left.center.popup { - margin: 0 0.71428571em 0 0; - transform-origin: right 50%; -} - -.ui.right.center.popup { - margin: 0 0 0 0.71428571em; - transform-origin: left 50%; -} - -/* Extending from Bottom */ - -.ui.bottom.popup { - margin: 0.71428571em 0 0; -} - -.ui.bottom.left.popup { - transform-origin: left top; -} - -.ui.bottom.center.popup { - transform-origin: center top; -} - -.ui.bottom.right.popup { - transform-origin: right top; -} - -/*-------------- - Pointer - ---------------*/ - -/*--- Below ---*/ - -.ui.bottom.center.popup:before { - margin-left: -0.30714286em; - top: -0.30714286em; - left: 50%; - right: auto; - bottom: auto; - box-shadow: -1px -1px 0 0 #bababc; -} - -.ui.bottom.left.popup { - margin-left: 0; -} - -/*rtl:rename*/ - -.ui.bottom.left.popup:before { - top: -0.30714286em; - left: 1em; - right: auto; - bottom: auto; - margin-left: 0; - box-shadow: -1px -1px 0 0 #bababc; -} - -.ui.bottom.right.popup { - margin-right: 0; -} - -/*rtl:rename*/ - -.ui.bottom.right.popup:before { - top: -0.30714286em; - right: 1em; - bottom: auto; - left: auto; - margin-left: 0; - box-shadow: -1px -1px 0 0 #bababc; -} - -/*--- Above ---*/ - -.ui.top.center.popup:before { - top: auto; - right: auto; - bottom: -0.30714286em; - left: 50%; - margin-left: -0.30714286em; -} - -.ui.top.left.popup { - margin-left: 0; -} - -/*rtl:rename*/ - -.ui.top.left.popup:before { - bottom: -0.30714286em; - left: 1em; - top: auto; - right: auto; - margin-left: 0; -} - -.ui.top.right.popup { - margin-right: 0; -} - -/*rtl:rename*/ - -.ui.top.right.popup:before { - bottom: -0.30714286em; - right: 1em; - top: auto; - left: auto; - margin-left: 0; -} - -/*--- Left Center ---*/ - -/*rtl:rename*/ - -.ui.left.center.popup:before { - top: 50%; - right: -0.30714286em; - bottom: auto; - left: auto; - margin-top: -0.30714286em; - box-shadow: 1px -1px 0 0 #bababc; -} - -/*--- Right Center ---*/ - -/*rtl:rename*/ - -.ui.right.center.popup:before { - top: 50%; - left: -0.30714286em; - bottom: auto; - right: auto; - margin-top: -0.30714286em; - box-shadow: -1px 1px 0 0 #bababc; -} - -.ui.right.center.popup:before, -.ui.left.center.popup:before { - background: #FFFFFF; -} - -/* Arrow Color By Location */ - -.ui.bottom.popup:before { - background: #FFFFFF; -} - -.ui.top.popup:before { - background: #FFFFFF; -} - -/* Inverted Arrow Color */ - -.ui.inverted.bottom.popup:before { - background: #1B1C1D; -} - -.ui.inverted.right.center.popup:before, -.ui.inverted.left.center.popup:before { - background: #1B1C1D; -} - -.ui.inverted.top.popup:before { - background: #1B1C1D; -} - -/******************************* - Coupling -*******************************/ - -/* Immediate Nested Grid */ - -.ui.popup > .ui.grid:not(.padded) { - width: calc(100% + 1.75rem); - margin: -0.7rem -0.875rem; -} - -/******************************* - States -*******************************/ - -.ui.loading.popup { - display: block; - visibility: hidden; - z-index: -1; -} - -.ui.animating.popup, -.ui.visible.popup { - display: block; -} - -.ui.visible.popup { - transform: translateZ(0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; -} - -/******************************* - Variations -*******************************/ - -/*-------------- - Basic - ---------------*/ - -.ui.basic.popup:before { - display: none; -} - -.ui.fixed.popup { - width: 250px; -} - -/*-------------- - Wide - ---------------*/ - -.ui.wide.popup { - max-width: 350px; -} - -.ui.wide.popup.fixed { - width: 350px; -} - -.ui[class*="very wide"].popup { - max-width: 550px; -} - -.ui[class*="very wide"].popup.fixed { - width: 550px; -} - -@media only screen and (max-width: 767.98px) { - .ui.wide.popup, - .ui[class*="very wide"].popup { - max-width: 250px; - } - - .ui.wide.popup.fixed, - .ui[class*="very wide"].popup.fixed { - width: 250px; - } -} - -/*-------------- - Fluid - ---------------*/ - -.ui.fluid.popup { - width: 100%; - max-width: none; -} - -/*-------------- - Colors - ---------------*/ - -/* Inverted colors */ - -.ui.inverted.popup { - background: #1B1C1D; - color: #FFFFFF; - border: none; - box-shadow: none; -} - -.ui.inverted.popup .header { - background-color: none; - color: #FFFFFF; -} - -.ui.inverted.popup:before { - background-color: #1B1C1D; - box-shadow: none !important; -} - -/*-------------- - Flowing - ---------------*/ - -.ui.flowing.popup { - max-width: none; -} - -/*-------------- - Sizes ----------------*/ - -.ui.popup { - font-size: 1rem; -} - -.ui.mini.popup { - font-size: 0.78571429rem; -} - -.ui.tiny.popup { - font-size: 0.85714286rem; -} - -.ui.small.popup { - font-size: 0.92857143rem; -} - -.ui.large.popup { - font-size: 1.14285714rem; -} - -.ui.big.popup { - font-size: 1.28571429rem; -} - -.ui.huge.popup { - font-size: 1.42857143rem; -} - -.ui.massive.popup { - font-size: 1.71428571rem; -} - -/******************************* - Theme Overrides -*******************************/ - -/******************************* - User Overrides -*******************************/ /*! * # Fomantic-UI - Reset * http://github.com/fomantic/Fomantic-UI/ diff --git a/web_src/fomantic/build/semantic.js b/web_src/fomantic/build/semantic.js index dcf99410c29f..77c82ca6fdcd 100644 --- a/web_src/fomantic/build/semantic.js +++ b/web_src/fomantic/build/semantic.js @@ -10298,1548 +10298,6 @@ $.fn.modal.settings = { }; -})( jQuery, window, document ); - -/*! - * # Fomantic-UI - Popup - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -;(function ($, window, document, undefined) { - -'use strict'; - -$.isFunction = $.isFunction || function(obj) { - return typeof obj === "function" && typeof obj.nodeType !== "number"; -}; - -window = (typeof window != 'undefined' && window.Math == Math) - ? window - : (typeof self != 'undefined' && self.Math == Math) - ? self - : Function('return this')() -; - -$.fn.popup = function(parameters) { - var - $allModules = $(this), - $document = $(document), - $window = $(window), - $body = $('body'), - - moduleSelector = $allModules.selector || '', - - clickEvent = ('ontouchstart' in document.documentElement) - ? 'touchstart' - : 'click', - - time = new Date().getTime(), - performance = [], - - query = arguments[0], - methodInvoked = (typeof query == 'string'), - queryArguments = [].slice.call(arguments, 1), - - returnedValue - ; - $allModules - .each(function() { - var - settings = ( $.isPlainObject(parameters) ) - ? $.extend(true, {}, $.fn.popup.settings, parameters) - : $.extend({}, $.fn.popup.settings), - - selector = settings.selector, - className = settings.className, - error = settings.error, - metadata = settings.metadata, - namespace = settings.namespace, - - eventNamespace = '.' + settings.namespace, - moduleNamespace = 'module-' + namespace, - - $module = $(this), - $context = $(settings.context), - $scrollContext = $(settings.scrollContext), - $boundary = $(settings.boundary), - $target = (settings.target) - ? $(settings.target) - : $module, - - $popup, - $offsetParent, - - searchDepth = 0, - triedPositions = false, - openedWithTouch = false, - - element = this, - instance = $module.data(moduleNamespace), - - documentObserver, - elementNamespace, - id, - module - ; - - module = { - - // binds events - initialize: function() { - module.debug('Initializing', $module); - module.createID(); - module.bind.events(); - if(!module.exists() && settings.preserve) { - module.create(); - } - if(settings.observeChanges) { - module.observeChanges(); - } - module.instantiate(); - }, - - instantiate: function() { - module.verbose('Storing instance', module); - instance = module; - $module - .data(moduleNamespace, instance) - ; - }, - - observeChanges: function() { - if('MutationObserver' in window) { - documentObserver = new MutationObserver(module.event.documentChanged); - documentObserver.observe(document, { - childList : true, - subtree : true - }); - module.debug('Setting up mutation observer', documentObserver); - } - }, - - refresh: function() { - if(settings.popup) { - $popup = $(settings.popup).eq(0); - } - else { - if(settings.inline) { - $popup = $target.nextAll(selector.popup).eq(0); - settings.popup = $popup; - } - } - if(settings.popup) { - $popup.addClass(className.loading); - $offsetParent = module.get.offsetParent(); - $popup.removeClass(className.loading); - if(settings.movePopup && module.has.popup() && module.get.offsetParent($popup)[0] !== $offsetParent[0]) { - module.debug('Moving popup to the same offset parent as target'); - $popup - .detach() - .appendTo($offsetParent) - ; - } - } - else { - $offsetParent = (settings.inline) - ? module.get.offsetParent($target) - : module.has.popup() - ? module.get.offsetParent($popup) - : $body - ; - } - if( $offsetParent.is('html') && $offsetParent[0] !== $body[0] ) { - module.debug('Setting page as offset parent'); - $offsetParent = $body; - } - if( module.get.variation() ) { - module.set.variation(); - } - }, - - reposition: function() { - module.refresh(); - module.set.position(); - }, - - destroy: function() { - module.debug('Destroying previous module'); - if(documentObserver) { - documentObserver.disconnect(); - } - // remove element only if was created dynamically - if($popup && !settings.preserve) { - module.removePopup(); - } - // clear all timeouts - clearTimeout(module.hideTimer); - clearTimeout(module.showTimer); - // remove events - module.unbind.close(); - module.unbind.events(); - $module - .removeData(moduleNamespace) - ; - }, - - event: { - start: function(event) { - var - delay = ($.isPlainObject(settings.delay)) - ? settings.delay.show - : settings.delay - ; - clearTimeout(module.hideTimer); - if(!openedWithTouch || (openedWithTouch && settings.addTouchEvents) ) { - module.showTimer = setTimeout(module.show, delay); - } - }, - end: function() { - var - delay = ($.isPlainObject(settings.delay)) - ? settings.delay.hide - : settings.delay - ; - clearTimeout(module.showTimer); - module.hideTimer = setTimeout(module.hide, delay); - }, - touchstart: function(event) { - openedWithTouch = true; - if(settings.addTouchEvents) { - module.show(); - } - }, - resize: function() { - if( module.is.visible() ) { - module.set.position(); - } - }, - documentChanged: function(mutations) { - [].forEach.call(mutations, function(mutation) { - if(mutation.removedNodes) { - [].forEach.call(mutation.removedNodes, function(node) { - if(node == element || $(node).find(element).length > 0) { - module.debug('Element removed from DOM, tearing down events'); - module.destroy(); - } - }); - } - }); - }, - hideGracefully: function(event) { - var - $target = $(event.target), - isInDOM = $.contains(document.documentElement, event.target), - inPopup = ($target.closest(selector.popup).length > 0) - ; - // don't close on clicks inside popup - if(event && !inPopup && isInDOM) { - module.debug('Click occurred outside popup hiding popup'); - module.hide(); - } - else { - module.debug('Click was inside popup, keeping popup open'); - } - } - }, - - // generates popup html from metadata - create: function() { - var - html = module.get.html(), - title = module.get.title(), - content = module.get.content() - ; - - if(html || content || title) { - module.debug('Creating pop-up html'); - if(!html) { - html = settings.templates.popup({ - title : title, - content : content - }); - } - $popup = $('
') - .addClass(className.popup) - .data(metadata.activator, $module) - .html(html) - ; - if(settings.inline) { - module.verbose('Inserting popup element inline', $popup); - $popup - .insertAfter($module) - ; - } - else { - module.verbose('Appending popup element to body', $popup); - $popup - .appendTo( $context ) - ; - } - module.refresh(); - module.set.variation(); - - if(settings.hoverable) { - module.bind.popup(); - } - settings.onCreate.call($popup, element); - } - else if(settings.popup) { - $(settings.popup).data(metadata.activator, $module); - module.verbose('Used popup specified in settings'); - module.refresh(); - if(settings.hoverable) { - module.bind.popup(); - } - } - else if($target.next(selector.popup).length !== 0) { - module.verbose('Pre-existing popup found'); - settings.inline = true; - settings.popup = $target.next(selector.popup).data(metadata.activator, $module); - module.refresh(); - if(settings.hoverable) { - module.bind.popup(); - } - } - else { - module.debug('No content specified skipping display', element); - } - }, - - createID: function() { - id = (Math.random().toString(16) + '000000000').substr(2, 8); - elementNamespace = '.' + id; - module.verbose('Creating unique id for element', id); - }, - - // determines popup state - toggle: function() { - module.debug('Toggling pop-up'); - if( module.is.hidden() ) { - module.debug('Popup is hidden, showing pop-up'); - module.unbind.close(); - module.show(); - } - else { - module.debug('Popup is visible, hiding pop-up'); - module.hide(); - } - }, - - show: function(callback) { - callback = callback || function(){}; - module.debug('Showing pop-up', settings.transition); - if(module.is.hidden() && !( module.is.active() && module.is.dropdown()) ) { - if( !module.exists() ) { - module.create(); - } - if(settings.onShow.call($popup, element) === false) { - module.debug('onShow callback returned false, cancelling popup animation'); - return; - } - else if(!settings.preserve && !settings.popup) { - module.refresh(); - } - if( $popup && module.set.position() ) { - module.save.conditions(); - if(settings.exclusive) { - module.hideAll(); - } - module.animate.show(callback); - } - } - }, - - - hide: function(callback) { - callback = callback || function(){}; - if( module.is.visible() || module.is.animating() ) { - if(settings.onHide.call($popup, element) === false) { - module.debug('onHide callback returned false, cancelling popup animation'); - return; - } - module.remove.visible(); - module.unbind.close(); - module.restore.conditions(); - module.animate.hide(callback); - } - }, - - hideAll: function() { - $(selector.popup) - .filter('.' + className.popupVisible) - .each(function() { - $(this) - .data(metadata.activator) - .popup('hide') - ; - }) - ; - }, - exists: function() { - if(!$popup) { - return false; - } - if(settings.inline || settings.popup) { - return ( module.has.popup() ); - } - else { - return ( $popup.closest($context).length >= 1 ) - ? true - : false - ; - } - }, - - removePopup: function() { - if( module.has.popup() && !settings.popup) { - module.debug('Removing popup', $popup); - $popup.remove(); - $popup = undefined; - settings.onRemove.call($popup, element); - } - }, - - save: { - conditions: function() { - module.cache = { - title: $module.attr('title') - }; - if (module.cache.title) { - $module.removeAttr('title'); - } - module.verbose('Saving original attributes', module.cache.title); - } - }, - restore: { - conditions: function() { - if(module.cache && module.cache.title) { - $module.attr('title', module.cache.title); - module.verbose('Restoring original attributes', module.cache.title); - } - return true; - } - }, - supports: { - svg: function() { - return (typeof SVGGraphicsElement !== 'undefined'); - } - }, - animate: { - show: function(callback) { - callback = $.isFunction(callback) ? callback : function(){}; - if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { - module.set.visible(); - $popup - .transition({ - animation : settings.transition + ' in', - queue : false, - debug : settings.debug, - verbose : settings.verbose, - duration : settings.duration, - onComplete : function() { - module.bind.close(); - callback.call($popup, element); - settings.onVisible.call($popup, element); - } - }) - ; - } - else { - module.error(error.noTransition); - } - }, - hide: function(callback) { - callback = $.isFunction(callback) ? callback : function(){}; - module.debug('Hiding pop-up'); - if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { - $popup - .transition({ - animation : settings.transition + ' out', - queue : false, - duration : settings.duration, - debug : settings.debug, - verbose : settings.verbose, - onComplete : function() { - module.reset(); - callback.call($popup, element); - settings.onHidden.call($popup, element); - } - }) - ; - } - else { - module.error(error.noTransition); - } - } - }, - - change: { - content: function(html) { - $popup.html(html); - } - }, - - get: { - html: function() { - $module.removeData(metadata.html); - return $module.data(metadata.html) || settings.html; - }, - title: function() { - $module.removeData(metadata.title); - return $module.data(metadata.title) || settings.title; - }, - content: function() { - $module.removeData(metadata.content); - return $module.data(metadata.content) || settings.content || $module.attr('title'); - }, - variation: function() { - $module.removeData(metadata.variation); - return $module.data(metadata.variation) || settings.variation; - }, - popup: function() { - return $popup; - }, - popupOffset: function() { - return $popup.offset(); - }, - calculations: function() { - var - $popupOffsetParent = module.get.offsetParent($popup), - targetElement = $target[0], - isWindow = ($boundary[0] == window), - targetOffset = $target.offset(), - parentOffset = settings.inline || (settings.popup && settings.movePopup) - ? $target.offsetParent().offset() - : { top: 0, left: 0 }, - screenPosition = (isWindow) - ? { top: 0, left: 0 } - : $boundary.offset(), - calculations = {}, - scroll = (isWindow) - ? { top: $window.scrollTop(), left: $window.scrollLeft() } - : { top: 0, left: 0}, - screen - ; - calculations = { - // element which is launching popup - target : { - element : $target[0], - width : $target.outerWidth(), - height : $target.outerHeight(), - top : targetOffset.top - parentOffset.top, - left : targetOffset.left - parentOffset.left, - margin : {} - }, - // popup itself - popup : { - width : $popup.outerWidth(), - height : $popup.outerHeight() - }, - // offset container (or 3d context) - parent : { - width : $offsetParent.outerWidth(), - height : $offsetParent.outerHeight() - }, - // screen boundaries - screen : { - top : screenPosition.top, - left : screenPosition.left, - scroll: { - top : scroll.top, - left : scroll.left - }, - width : $boundary.width(), - height : $boundary.height() - } - }; - - // if popup offset context is not same as target, then adjust calculations - if($popupOffsetParent.get(0) !== $offsetParent.get(0)) { - var - popupOffset = $popupOffsetParent.offset() - ; - calculations.target.top -= popupOffset.top; - calculations.target.left -= popupOffset.left; - calculations.parent.width = $popupOffsetParent.outerWidth(); - calculations.parent.height = $popupOffsetParent.outerHeight(); - } - - // add in container calcs if fluid - if( settings.setFluidWidth && module.is.fluid() ) { - calculations.container = { - width: $popup.parent().outerWidth() - }; - calculations.popup.width = calculations.container.width; - } - - // add in margins if inline - calculations.target.margin.top = (settings.inline) - ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-top'), 10) - : 0 - ; - calculations.target.margin.left = (settings.inline) - ? module.is.rtl() - ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-right'), 10) - : parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-left'), 10) - : 0 - ; - // calculate screen boundaries - screen = calculations.screen; - calculations.boundary = { - top : screen.top + screen.scroll.top, - bottom : screen.top + screen.scroll.top + screen.height, - left : screen.left + screen.scroll.left, - right : screen.left + screen.scroll.left + screen.width - }; - return calculations; - }, - id: function() { - return id; - }, - startEvent: function() { - if(settings.on == 'hover') { - return 'mouseenter'; - } - else if(settings.on == 'focus') { - return 'focus'; - } - return false; - }, - scrollEvent: function() { - return 'scroll'; - }, - endEvent: function() { - if(settings.on == 'hover') { - return 'mouseleave'; - } - else if(settings.on == 'focus') { - return 'blur'; - } - return false; - }, - distanceFromBoundary: function(offset, calculations) { - var - distanceFromBoundary = {}, - popup, - boundary - ; - calculations = calculations || module.get.calculations(); - - // shorthand - popup = calculations.popup; - boundary = calculations.boundary; - - if(offset) { - distanceFromBoundary = { - top : (offset.top - boundary.top), - left : (offset.left - boundary.left), - right : (boundary.right - (offset.left + popup.width) ), - bottom : (boundary.bottom - (offset.top + popup.height) ) - }; - module.verbose('Distance from boundaries determined', offset, distanceFromBoundary); - } - return distanceFromBoundary; - }, - offsetParent: function($element) { - var - element = ($element !== undefined) - ? $element[0] - : $target[0], - parentNode = element.parentNode, - $node = $(parentNode) - ; - if(parentNode) { - var - is2D = ($node.css('transform') === 'none'), - isStatic = ($node.css('position') === 'static'), - isBody = $node.is('body') - ; - while(parentNode && !isBody && isStatic && is2D) { - parentNode = parentNode.parentNode; - $node = $(parentNode); - is2D = ($node.css('transform') === 'none'); - isStatic = ($node.css('position') === 'static'); - isBody = $node.is('body'); - } - } - return ($node && $node.length > 0) - ? $node - : $() - ; - }, - positions: function() { - return { - 'top left' : false, - 'top center' : false, - 'top right' : false, - 'bottom left' : false, - 'bottom center' : false, - 'bottom right' : false, - 'left center' : false, - 'right center' : false - }; - }, - nextPosition: function(position) { - var - positions = position.split(' '), - verticalPosition = positions[0], - horizontalPosition = positions[1], - opposite = { - top : 'bottom', - bottom : 'top', - left : 'right', - right : 'left' - }, - adjacent = { - left : 'center', - center : 'right', - right : 'left' - }, - backup = { - 'top left' : 'top center', - 'top center' : 'top right', - 'top right' : 'right center', - 'right center' : 'bottom right', - 'bottom right' : 'bottom center', - 'bottom center' : 'bottom left', - 'bottom left' : 'left center', - 'left center' : 'top left' - }, - adjacentsAvailable = (verticalPosition == 'top' || verticalPosition == 'bottom'), - oppositeTried = false, - adjacentTried = false, - nextPosition = false - ; - if(!triedPositions) { - module.verbose('All available positions available'); - triedPositions = module.get.positions(); - } - - module.debug('Recording last position tried', position); - triedPositions[position] = true; - - if(settings.prefer === 'opposite') { - nextPosition = [opposite[verticalPosition], horizontalPosition]; - nextPosition = nextPosition.join(' '); - oppositeTried = (triedPositions[nextPosition] === true); - module.debug('Trying opposite strategy', nextPosition); - } - if((settings.prefer === 'adjacent') && adjacentsAvailable ) { - nextPosition = [verticalPosition, adjacent[horizontalPosition]]; - nextPosition = nextPosition.join(' '); - adjacentTried = (triedPositions[nextPosition] === true); - module.debug('Trying adjacent strategy', nextPosition); - } - if(adjacentTried || oppositeTried) { - module.debug('Using backup position', nextPosition); - nextPosition = backup[position]; - } - return nextPosition; - } - }, - - set: { - position: function(position, calculations) { - - // exit conditions - if($target.length === 0 || $popup.length === 0) { - module.error(error.notFound); - return; - } - var - offset, - distanceAway, - target, - popup, - parent, - positioning, - popupOffset, - distanceFromBoundary - ; - - calculations = calculations || module.get.calculations(); - position = position || $module.data(metadata.position) || settings.position; - - offset = $module.data(metadata.offset) || settings.offset; - distanceAway = settings.distanceAway; - - // shorthand - target = calculations.target; - popup = calculations.popup; - parent = calculations.parent; - - if(module.should.centerArrow(calculations)) { - module.verbose('Adjusting offset to center arrow on small target element'); - if(position == 'top left' || position == 'bottom left') { - offset += (target.width / 2); - offset -= settings.arrowPixelsFromEdge; - } - if(position == 'top right' || position == 'bottom right') { - offset -= (target.width / 2); - offset += settings.arrowPixelsFromEdge; - } - } - - if(target.width === 0 && target.height === 0 && !module.is.svg(target.element)) { - module.debug('Popup target is hidden, no action taken'); - return false; - } - - if(settings.inline) { - module.debug('Adding margin to calculation', target.margin); - if(position == 'left center' || position == 'right center') { - offset += target.margin.top; - distanceAway += -target.margin.left; - } - else if (position == 'top left' || position == 'top center' || position == 'top right') { - offset += target.margin.left; - distanceAway -= target.margin.top; - } - else { - offset += target.margin.left; - distanceAway += target.margin.top; - } - } - - module.debug('Determining popup position from calculations', position, calculations); - - if (module.is.rtl()) { - position = position.replace(/left|right/g, function (match) { - return (match == 'left') - ? 'right' - : 'left' - ; - }); - module.debug('RTL: Popup position updated', position); - } - - // if last attempt use specified last resort position - if(searchDepth == settings.maxSearchDepth && typeof settings.lastResort === 'string') { - position = settings.lastResort; - } - - switch (position) { - case 'top left': - positioning = { - top : 'auto', - bottom : parent.height - target.top + distanceAway, - left : target.left + offset, - right : 'auto' - }; - break; - case 'top center': - positioning = { - bottom : parent.height - target.top + distanceAway, - left : target.left + (target.width / 2) - (popup.width / 2) + offset, - top : 'auto', - right : 'auto' - }; - break; - case 'top right': - positioning = { - bottom : parent.height - target.top + distanceAway, - right : parent.width - target.left - target.width - offset, - top : 'auto', - left : 'auto' - }; - break; - case 'left center': - positioning = { - top : target.top + (target.height / 2) - (popup.height / 2) + offset, - right : parent.width - target.left + distanceAway, - left : 'auto', - bottom : 'auto' - }; - break; - case 'right center': - positioning = { - top : target.top + (target.height / 2) - (popup.height / 2) + offset, - left : target.left + target.width + distanceAway, - bottom : 'auto', - right : 'auto' - }; - break; - case 'bottom left': - positioning = { - top : target.top + target.height + distanceAway, - left : target.left + offset, - bottom : 'auto', - right : 'auto' - }; - break; - case 'bottom center': - positioning = { - top : target.top + target.height + distanceAway, - left : target.left + (target.width / 2) - (popup.width / 2) + offset, - bottom : 'auto', - right : 'auto' - }; - break; - case 'bottom right': - positioning = { - top : target.top + target.height + distanceAway, - right : parent.width - target.left - target.width - offset, - left : 'auto', - bottom : 'auto' - }; - break; - } - if(positioning === undefined) { - module.error(error.invalidPosition, position); - } - - module.debug('Calculated popup positioning values', positioning); - - // tentatively place on stage - $popup - .css(positioning) - .removeClass(className.position) - .addClass(position) - .addClass(className.loading) - ; - - popupOffset = module.get.popupOffset(); - - // see if any boundaries are surpassed with this tentative position - distanceFromBoundary = module.get.distanceFromBoundary(popupOffset, calculations); - - if(!settings.forcePosition && module.is.offstage(distanceFromBoundary, position) ) { - module.debug('Position is outside viewport', position); - if(searchDepth < settings.maxSearchDepth) { - searchDepth++; - position = module.get.nextPosition(position); - module.debug('Trying new position', position); - return ($popup) - ? module.set.position(position, calculations) - : false - ; - } - else { - if(settings.lastResort) { - module.debug('No position found, showing with last position'); - } - else { - module.debug('Popup could not find a position to display', $popup); - module.error(error.cannotPlace, element); - module.remove.attempts(); - module.remove.loading(); - module.reset(); - settings.onUnplaceable.call($popup, element); - return false; - } - } - } - module.debug('Position is on stage', position); - module.remove.attempts(); - module.remove.loading(); - if( settings.setFluidWidth && module.is.fluid() ) { - module.set.fluidWidth(calculations); - } - return true; - }, - - fluidWidth: function(calculations) { - calculations = calculations || module.get.calculations(); - module.debug('Automatically setting element width to parent width', calculations.parent.width); - $popup.css('width', calculations.container.width); - }, - - variation: function(variation) { - variation = variation || module.get.variation(); - if(variation && module.has.popup() ) { - module.verbose('Adding variation to popup', variation); - $popup.addClass(variation); - } - }, - - visible: function() { - $module.addClass(className.visible); - } - }, - - remove: { - loading: function() { - $popup.removeClass(className.loading); - }, - variation: function(variation) { - variation = variation || module.get.variation(); - if(variation) { - module.verbose('Removing variation', variation); - $popup.removeClass(variation); - } - }, - visible: function() { - $module.removeClass(className.visible); - }, - attempts: function() { - module.verbose('Resetting all searched positions'); - searchDepth = 0; - triedPositions = false; - } - }, - - bind: { - events: function() { - module.debug('Binding popup events to module'); - if(settings.on == 'click') { - $module - .on(clickEvent + eventNamespace, module.toggle) - ; - } - if(settings.on == 'hover') { - $module - .on('touchstart' + eventNamespace, module.event.touchstart) - ; - } - if( module.get.startEvent() ) { - $module - .on(module.get.startEvent() + eventNamespace, module.event.start) - .on(module.get.endEvent() + eventNamespace, module.event.end) - ; - } - if(settings.target) { - module.debug('Target set to element', $target); - } - $window.on('resize' + elementNamespace, module.event.resize); - }, - popup: function() { - module.verbose('Allowing hover events on popup to prevent closing'); - if( $popup && module.has.popup() ) { - $popup - .on('mouseenter' + eventNamespace, module.event.start) - .on('mouseleave' + eventNamespace, module.event.end) - ; - } - }, - close: function() { - if(settings.hideOnScroll === true || (settings.hideOnScroll == 'auto' && settings.on != 'click')) { - module.bind.closeOnScroll(); - } - if(module.is.closable()) { - module.bind.clickaway(); - } - else if(settings.on == 'hover' && openedWithTouch) { - module.bind.touchClose(); - } - }, - closeOnScroll: function() { - module.verbose('Binding scroll close event to document'); - $scrollContext - .one(module.get.scrollEvent() + elementNamespace, module.event.hideGracefully) - ; - }, - touchClose: function() { - module.verbose('Binding popup touchclose event to document'); - $document - .on('touchstart' + elementNamespace, function(event) { - module.verbose('Touched away from popup'); - module.event.hideGracefully.call(element, event); - }) - ; - }, - clickaway: function() { - module.verbose('Binding popup close event to document'); - $document - .on(clickEvent + elementNamespace, function(event) { - module.verbose('Clicked away from popup'); - module.event.hideGracefully.call(element, event); - }) - ; - } - }, - - unbind: { - events: function() { - $window - .off(elementNamespace) - ; - $module - .off(eventNamespace) - ; - }, - close: function() { - $document - .off(elementNamespace) - ; - $scrollContext - .off(elementNamespace) - ; - }, - }, - - has: { - popup: function() { - return ($popup && $popup.length > 0); - } - }, - - should: { - centerArrow: function(calculations) { - return !module.is.basic() && calculations.target.width <= (settings.arrowPixelsFromEdge * 2); - }, - }, - - is: { - closable: function() { - if(settings.closable == 'auto') { - if(settings.on == 'hover') { - return false; - } - return true; - } - return settings.closable; - }, - offstage: function(distanceFromBoundary, position) { - var - offstage = [] - ; - // return boundaries that have been surpassed - $.each(distanceFromBoundary, function(direction, distance) { - if(distance < -settings.jitter) { - module.debug('Position exceeds allowable distance from edge', direction, distance, position); - offstage.push(direction); - } - }); - if(offstage.length > 0) { - return true; - } - else { - return false; - } - }, - svg: function(element) { - return module.supports.svg() && (element instanceof SVGGraphicsElement); - }, - basic: function() { - return $module.hasClass(className.basic); - }, - active: function() { - return $module.hasClass(className.active); - }, - animating: function() { - return ($popup !== undefined && $popup.hasClass(className.animating) ); - }, - fluid: function() { - return ($popup !== undefined && $popup.hasClass(className.fluid)); - }, - visible: function() { - return ($popup !== undefined && $popup.hasClass(className.popupVisible)); - }, - dropdown: function() { - return $module.hasClass(className.dropdown); - }, - hidden: function() { - return !module.is.visible(); - }, - rtl: function () { - return $module.attr('dir') === 'rtl' || $module.css('direction') === 'rtl'; - } - }, - - reset: function() { - module.remove.visible(); - if(settings.preserve) { - if($.fn.transition !== undefined) { - $popup - .transition('remove transition') - ; - } - } - else { - module.removePopup(); - } - }, - - setting: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, settings, name); - } - else if(value !== undefined) { - settings[name] = value; - } - else { - return settings[name]; - } - }, - internal: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, module, name); - } - else if(value !== undefined) { - module[name] = value; - } - else { - return module[name]; - } - }, - debug: function() { - if(!settings.silent && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.debug.apply(console, arguments); - } - } - }, - verbose: function() { - if(!settings.silent && settings.verbose && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.verbose.apply(console, arguments); - } - } - }, - error: function() { - if(!settings.silent) { - module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); - module.error.apply(console, arguments); - } - }, - performance: { - log: function(message) { - var - currentTime, - executionTime, - previousTime - ; - if(settings.performance) { - currentTime = new Date().getTime(); - previousTime = time || currentTime; - executionTime = currentTime - previousTime; - time = currentTime; - performance.push({ - 'Name' : message[0], - 'Arguments' : [].slice.call(message, 1) || '', - 'Element' : element, - 'Execution Time' : executionTime - }); - } - clearTimeout(module.performance.timer); - module.performance.timer = setTimeout(module.performance.display, 500); - }, - display: function() { - var - title = settings.name + ':', - totalTime = 0 - ; - time = false; - clearTimeout(module.performance.timer); - $.each(performance, function(index, data) { - totalTime += data['Execution Time']; - }); - title += ' ' + totalTime + 'ms'; - if(moduleSelector) { - title += ' \'' + moduleSelector + '\''; - } - if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { - console.groupCollapsed(title); - if(console.table) { - console.table(performance); - } - else { - $.each(performance, function(index, data) { - console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); - }); - } - console.groupEnd(); - } - performance = []; - } - }, - invoke: function(query, passedArguments, context) { - var - object = instance, - maxDepth, - found, - response - ; - passedArguments = passedArguments || queryArguments; - context = element || context; - if(typeof query == 'string' && object !== undefined) { - query = query.split(/[\. ]/); - maxDepth = query.length - 1; - $.each(query, function(depth, value) { - var camelCaseValue = (depth != maxDepth) - ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) - : query - ; - if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { - object = object[camelCaseValue]; - } - else if( object[camelCaseValue] !== undefined ) { - found = object[camelCaseValue]; - return false; - } - else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { - object = object[value]; - } - else if( object[value] !== undefined ) { - found = object[value]; - return false; - } - else { - return false; - } - }); - } - if ( $.isFunction( found ) ) { - response = found.apply(context, passedArguments); - } - else if(found !== undefined) { - response = found; - } - if(Array.isArray(returnedValue)) { - returnedValue.push(response); - } - else if(returnedValue !== undefined) { - returnedValue = [returnedValue, response]; - } - else if(response !== undefined) { - returnedValue = response; - } - return found; - } - }; - - if(methodInvoked) { - if(instance === undefined) { - module.initialize(); - } - module.invoke(query); - } - else { - if(instance !== undefined) { - instance.invoke('destroy'); - } - module.initialize(); - } - }) - ; - - return (returnedValue !== undefined) - ? returnedValue - : this - ; -}; - -$.fn.popup.settings = { - - name : 'Popup', - - // module settings - silent : false, - debug : false, - verbose : false, - performance : true, - namespace : 'popup', - - // whether it should use dom mutation observers - observeChanges : true, - - // callback only when element added to dom - onCreate : function(){}, - - // callback before element removed from dom - onRemove : function(){}, - - // callback before show animation - onShow : function(){}, - - // callback after show animation - onVisible : function(){}, - - // callback before hide animation - onHide : function(){}, - - // callback when popup cannot be positioned in visible screen - onUnplaceable : function(){}, - - // callback after hide animation - onHidden : function(){}, - - // when to show popup - on : 'hover', - - // element to use to determine if popup is out of boundary - boundary : window, - - // whether to add touchstart events when using hover - addTouchEvents : true, - - // default position relative to element - position : 'top left', - - // if given position should be used regardless if popup fits - forcePosition : false, - - // name of variation to use - variation : '', - - // whether popup should be moved to context - movePopup : true, - - // element which popup should be relative to - target : false, - - // jq selector or element that should be used as popup - popup : false, - - // popup should remain inline next to activator - inline : false, - - // popup should be removed from page on hide - preserve : false, - - // popup should not close when being hovered on - hoverable : false, - - // explicitly set content - content : false, - - // explicitly set html - html : false, - - // explicitly set title - title : false, - - // whether automatically close on clickaway when on click - closable : true, - - // automatically hide on scroll - hideOnScroll : 'auto', - - // hide other popups on show - exclusive : false, - - // context to attach popups - context : 'body', - - // context for binding scroll events - scrollContext : window, - - // position to prefer when calculating new position - prefer : 'opposite', - - // specify position to appear even if it doesn't fit - lastResort : false, - - // number of pixels from edge of popup to pointing arrow center (used from centering) - arrowPixelsFromEdge: 20, - - // delay used to prevent accidental refiring of animations due to user error - delay : { - show : 50, - hide : 70 - }, - - // whether fluid variation should assign width explicitly - setFluidWidth : true, - - // transition settings - duration : 200, - transition : 'scale', - - // distance away from activating element in px - distanceAway : 0, - - // number of pixels an element is allowed to be "offstage" for a position to be chosen (allows for rounding) - jitter : 2, - - // offset on aligning axis from calculated position - offset : 0, - - // maximum times to look for a position before failing (9 positions total) - maxSearchDepth : 15, - - error: { - invalidPosition : 'The position you specified is not a valid position', - cannotPlace : 'Popup does not fit within the boundaries of the viewport', - method : 'The method you called is not defined.', - noTransition : 'This module requires ui transitions ', - notFound : 'The target or popup you specified does not exist on the page' - }, - - metadata: { - activator : 'activator', - content : 'content', - html : 'html', - offset : 'offset', - position : 'position', - title : 'title', - variation : 'variation' - }, - - className : { - active : 'active', - basic : 'basic', - animating : 'animating', - dropdown : 'dropdown', - fluid : 'fluid', - loading : 'loading', - popup : 'ui popup', - position : 'top left center bottom right', - visible : 'visible', - popupVisible : 'visible' - }, - - selector : { - popup : '.ui.popup' - }, - - templates: { - escape: function(string) { - var - badChars = /[<>"'`]/g, - shouldEscape = /[&<>"'`]/, - escape = { - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" - }, - escapedChar = function(chr) { - return escape[chr]; - } - ; - if(shouldEscape.test(string)) { - string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); - return string.replace(badChars, escapedChar); - } - return string; - }, - popup: function(text) { - var - html = '', - escape = $.fn.popup.settings.templates.escape - ; - if(typeof text !== undefined) { - if(typeof text.title !== undefined && text.title) { - text.title = escape(text.title); - html += '
' + text.title + '
'; - } - if(typeof text.content !== undefined && text.content) { - text.content = escape(text.content); - html += '
' + text.content + '
'; - } - } - return html; - } - } - -}; - - })( jQuery, window, document ); /*! diff --git a/web_src/fomantic/semantic.json b/web_src/fomantic/semantic.json index ff45cc465c2c..a94adab85cf0 100644 --- a/web_src/fomantic/semantic.json +++ b/web_src/fomantic/semantic.json @@ -44,7 +44,6 @@ "menu", "message", "modal", - "popup", "reset", "search", "segment", diff --git a/web_src/js/components/DashboardRepoList.js b/web_src/js/components/DashboardRepoList.js index 36caaf2f5bcf..cbbc12c2c4f6 100644 --- a/web_src/js/components/DashboardRepoList.js +++ b/web_src/js/components/DashboardRepoList.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import $ from 'jquery'; import {initVueSvg, vueDelimiters} from './VueComponentLoader.js'; +import {initTooltip} from '../modules/tippy.js'; const {appSubUrl, assetUrlPrefix, pageData} = window.config; @@ -138,7 +139,9 @@ function initVueComponents() { mounted() { this.changeReposFilter(this.reposFilter); - $(this.$el).find('.tooltip').popup(); + for (const el of this.$el.querySelectorAll('.tooltip')) { + initTooltip(el); + } $(this.$el).find('.dropdown').dropdown(); this.setCheckboxes(); Vue.nextTick(() => { diff --git a/web_src/js/features/clipboard.js b/web_src/js/features/clipboard.js index e4a5c4f448d8..85324303e347 100644 --- a/web_src/js/features/clipboard.js +++ b/web_src/js/features/clipboard.js @@ -1,24 +1,15 @@ -import $ from 'jquery'; +import {showTemporaryTooltip} from '../modules/tippy.js'; const {copy_success, copy_error} = window.config.i18n; -function onSuccess(btn) { - btn.setAttribute('data-variation', 'inverted tiny'); - $(btn).popup('destroy'); - const oldContent = btn.getAttribute('data-content'); - btn.setAttribute('data-content', copy_success); - $(btn).popup('show'); - btn.setAttribute('data-content', oldContent || ''); +export async function copyToClipboard(text) { + try { + await navigator.clipboard.writeText(text); + } catch { + return fallbackCopyToClipboard(text); + } + return true; } -function onError(btn) { - btn.setAttribute('data-variation', 'inverted tiny'); - const oldContent = btn.getAttribute('data-content'); - $(btn).popup('destroy'); - btn.setAttribute('data-content', copy_error); - $(btn).popup('show'); - btn.setAttribute('data-content', oldContent || ''); -} - // Fallback to use if navigator.clipboard doesn't exist. Achieved via creating // a temporary textarea element, selecting the text, and using document.execCommand @@ -60,16 +51,8 @@ export default function initGlobalCopyToClipboardListener() { e.preventDefault(); (async() => { - try { - await navigator.clipboard.writeText(text); - onSuccess(target); - } catch { - if (fallbackCopyToClipboard(text)) { - onSuccess(target); - } else { - onError(target); - } - } + const success = await copyToClipboard(text); + showTemporaryTooltip(target, success ? copy_success : copy_error); })(); break; diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index 025b44d87d81..1776f6577d9a 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -6,6 +6,7 @@ import {initCompColorPicker} from './comp/ColorPicker.js'; import {showGlobalErrorMessage} from '../bootstrap.js'; import {attachDropdownAria} from './aria.js'; import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js'; +import {initTooltip} from '../modules/tippy.js'; const {appUrl, csrfToken} = window.config; @@ -62,18 +63,10 @@ export function initGlobalButtonClickOnEnter() { }); } -export function initPopup(target) { - const $el = $(target); - const attr = $el.attr('data-variation'); - const attrs = attr ? attr.split(' ') : []; - const variations = new Set([...attrs, 'inverted', 'tiny']); - $el.attr('data-variation', [...variations].join(' ')).popup(); -} - -export function initGlobalPopups() { - $('.tooltip').each((_, el) => { - initPopup(el); - }); +export function initGlobalTooltips() { + for (const el of document.getElementsByClassName('tooltip')) { + initTooltip(el); + } } export function initGlobalCommon() { @@ -106,7 +99,12 @@ export function initGlobalCommon() { $uiDropdowns.filter('.jump').dropdown({ action: 'hide', onShow() { - $('.tooltip').popup('hide'); + // hide associated tooltip while dropdown is open + this._tippy?.hide(); + this._tippy?.disable(); + }, + onHide() { + this._tippy?.enable(); }, fullTextSearch: 'exact' }); @@ -122,13 +120,6 @@ export function initGlobalCommon() { $('.ui.checkbox').checkbox(); - $('.top.menu .tooltip').popup({ - onShow() { - if ($('.top.menu .menu.transition').hasClass('visible')) { - return false; - } - } - }); $('.tabular.menu .item').tab(); $('.tabable.menu .item').tab(); diff --git a/web_src/js/features/comp/ReactionSelector.js b/web_src/js/features/comp/ReactionSelector.js index 272ea45cddea..26c9af2ff3ab 100644 --- a/web_src/js/features/comp/ReactionSelector.js +++ b/web_src/js/features/comp/ReactionSelector.js @@ -1,16 +1,20 @@ import $ from 'jquery'; +import {createTippy} from '../../modules/tippy.js'; + const {csrfToken} = window.config; export function initCompReactionSelector(parent) { - let reactions = ''; + let selector = 'a.label'; if (!parent) { parent = $(document); - reactions = '.reactions > '; + selector = `.reactions ${selector}`; } - parent.find(`${reactions}a.label`).popup({position: 'bottom left', metadata: {content: 'title', title: 'none'}}); + for (const el of parent[0].querySelectorAll(selector)) { + createTippy(el, {placement: 'bottom-start', content: el.getAttribute('data-title')}); + } - parent.find(`.select-reaction > .menu > .item, ${reactions}a.label`).on('click', function (e) { + parent.find(`.select-reaction > .menu > .item, ${selector}`).on('click', function (e) { e.preventDefault(); if ($(this).hasClass('disabled')) return; diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js index 8562ba00723b..002a25f6ed82 100644 --- a/web_src/js/features/repo-code.js +++ b/web_src/js/features/repo-code.js @@ -1,6 +1,8 @@ import $ from 'jquery'; import {svg} from '../svg.js'; import {invertFileFolding} from './file-fold.js'; +import {createTippy} from '../modules/tippy.js'; +import {copyToClipboard} from './clipboard.js'; function changeHash(hash) { if (window.history.pushState) { @@ -39,13 +41,13 @@ function selectRange($list, $select, $from) { $viewGitBlame.attr('href', href); }; - const updateCopyPermalinkHref = function(anchor) { + const updateCopyPermalinkUrl = function(anchor) { if ($copyPermalink.length === 0) { return; } - let link = $copyPermalink.attr('data-clipboard-text'); + let link = $copyPermalink.attr('data-url'); link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`; - $copyPermalink.attr('data-clipboard-text', link); + $copyPermalink.attr('data-url', link); }; if ($from) { @@ -67,7 +69,7 @@ function selectRange($list, $select, $from) { updateIssueHref(`L${a}-L${b}`); updateViewGitBlameFragment(`L${a}-L${b}`); - updateCopyPermalinkHref(`L${a}-L${b}`); + updateCopyPermalinkUrl(`L${a}-L${b}`); return; } } @@ -76,17 +78,36 @@ function selectRange($list, $select, $from) { updateIssueHref($select.attr('rel')); updateViewGitBlameFragment($select.attr('rel')); - updateCopyPermalinkHref($select.attr('rel')); + updateCopyPermalinkUrl($select.attr('rel')); } function showLineButton() { - if ($('.code-line-menu').length === 0) return; - $('.code-line-button').remove(); - $('.code-view td.lines-code.active').closest('tr').find('td:eq(0)').first().prepend( - $(``) - ); - $('.code-line-menu').appendTo($('.code-view')); - $('.code-line-button').popup({popup: $('.code-line-menu'), on: 'click'}); + const menu = document.querySelector('.code-line-menu'); + if (!menu) return; + + // remove all other line buttons + for (const el of document.querySelectorAll('.code-line-button')) { + el.remove(); + } + + // find active row and add button + const tr = document.querySelector('.code-view td.lines-code.active').closest('tr'); + const td = tr.querySelector('td'); + const btn = document.createElement('button'); + btn.classList.add('code-line-button'); + btn.innerHTML = svg('octicon-kebab-horizontal'); + td.prepend(btn); + + // put a copy of the menu back into DOM for the next click + btn.closest('.code-view').appendChild(menu.cloneNode(true)); + + createTippy(btn, { + trigger: 'click', + content: menu, + placement: 'right-start', + role: 'menu', + interactive: 'true', + }); } export function initRepoCodeView() { @@ -159,4 +180,9 @@ export function initRepoCodeView() { const blob = await $.get(`${url}?${query}&anchor=${anchor}`); currentTarget.closest('tr').outerHTML = blob; }); + $(document).on('click', '.copy-line-permalink', async (e) => { + const success = await copyToClipboard(e.currentTarget.getAttribute('data-url')); + if (!success) return; + document.querySelector('.code-line-button')?._tippy?.hide(); + }); } diff --git a/web_src/js/features/repo-commit.js b/web_src/js/features/repo-commit.js index 94fca7a9c203..170284f1010e 100644 --- a/web_src/js/features/repo-commit.js +++ b/web_src/js/features/repo-commit.js @@ -1,4 +1,5 @@ import $ from 'jquery'; +import {createTippy} from '../modules/tippy.js'; const {csrfToken} = window.config; @@ -57,13 +58,13 @@ export function initRepoCommitLastCommitLoader() { export function initCommitStatuses() { $('.commit-statuses-trigger').each(function () { - const positionRight = $('.repository.file.list').length > 0 || $('.repository.diff').length > 0; - const popupPosition = positionRight ? 'right center' : 'left center'; - $(this) - .popup({ - on: 'click', - lastResort: popupPosition, // prevent error message "Popup does not fit within the boundaries of the viewport" - position: popupPosition, - }); + const top = $('.repository.file.list').length > 0 || $('.repository.diff').length > 0; + + createTippy(this, { + trigger: 'click', + content: this.nextElementSibling, + placement: top ? 'top-start' : 'bottom-start', + interactive: true, + }); }); } diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js index 92d8ecfc8609..59e0c147d9ea 100644 --- a/web_src/js/features/repo-diff.js +++ b/web_src/js/features/repo-diff.js @@ -3,7 +3,7 @@ import {initCompReactionSelector} from './comp/ReactionSelector.js'; import {initRepoIssueContentHistory} from './repo-issue-content.js'; import {validateTextareaNonEmpty} from './comp/EasyMDE.js'; import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles} from './pull-view-file.js'; -import {initPopup} from './common-global.js'; +import {initTooltip} from '../modules/tippy.js'; const {csrfToken} = window.config; @@ -53,7 +53,7 @@ export function initRepoDiffConversationForm() { const newConversationHolder = $(await $.post(form.attr('action'), form.serialize())); const {path, side, idx} = newConversationHolder.data(); - initPopup(newConversationHolder.find('.tooltip')); + initTooltip(newConversationHolder.find('.tooltip')); form.closest('.conversation-holder').replaceWith(newConversationHolder); if (form.closest('tr').data('line-type') === 'same') { $(`[data-path="${path}"] a.add-code-comment[data-idx="${idx}"]`).addClass('invisible'); diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 12900c2455d8..9dbe78edf51b 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -4,6 +4,7 @@ import attachTribute from './tribute.js'; import {createCommentEasyMDE, getAttachedEasyMDE} from './comp/EasyMDE.js'; import {initEasyMDEImagePaste} from './comp/ImagePaste.js'; import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js'; +import {initTooltip, showTemporaryTooltip} from '../modules/tippy.js'; const {appSubUrl, csrfToken} = window.config; @@ -278,7 +279,8 @@ export function initRepoPullRequestAllowMaintainerEdit() { const promptTip = $checkbox.attr('data-prompt-tip'); const promptError = $checkbox.attr('data-prompt-error'); - $checkbox.popup({content: promptTip}); + + initTooltip($checkbox[0], {content: promptTip}); $checkbox.checkbox({ 'onChange': () => { const checked = $checkbox.checkbox('is checked'); @@ -288,14 +290,7 @@ export function initRepoPullRequestAllowMaintainerEdit() { $.ajax({url, type: 'POST', data: {_csrf: csrfToken, allow_maintainer_edit: checked}, error: () => { - $checkbox.popup({ - content: promptError, - onHidden: () => { - // the error popup should be shown only once, then we restore the popup to the default message - $checkbox.popup({content: promptTip}); - }, - }); - $checkbox.popup('show'); + showTemporaryTooltip($checkbox[0], promptError); }, complete: () => { $checkbox.checkbox('set enabled'); diff --git a/web_src/js/features/stopwatch.js b/web_src/js/features/stopwatch.js index c3aa79b7676e..ffa2ad4189b4 100644 --- a/web_src/js/features/stopwatch.js +++ b/web_src/js/features/stopwatch.js @@ -1,5 +1,6 @@ import $ from 'jquery'; import prettyMilliseconds from 'pretty-ms'; +import {createTippy} from '../modules/tippy.js'; const {appSubUrl, csrfToken, notificationSettings, enableTimeTracking} = window.config; @@ -8,21 +9,21 @@ export function initStopwatch() { return; } - const stopwatchEl = $('.active-stopwatch-trigger'); + const stopwatchEl = document.querySelector('.active-stopwatch-trigger'); + const stopwatchPopup = document.querySelector('.active-stopwatch-popup'); - if (!stopwatchEl.length) { + if (!stopwatchEl || !stopwatchPopup) { return; } - stopwatchEl.removeAttr('href'); // intended for noscript mode only - stopwatchEl.popup({ - position: 'bottom right', - hoverable: true, - }); + stopwatchEl.removeAttribute('href'); // intended for noscript mode only - // form handlers - $('form > button', stopwatchEl).on('click', function () { - $(this).parent().trigger('submit'); + createTippy(stopwatchEl, { + content: stopwatchPopup, + placement: 'bottom-end', + trigger: 'click', + maxWidth: 'none', + interactive: true, }); // global stop watch (in the head_navbar), it should always work in any case either the EventSource or the PeriodicPoller is used. diff --git a/web_src/js/index.js b/web_src/js/index.js index 6f872b535337..b96e79c3c87f 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -56,7 +56,7 @@ import { initGlobalFormDirtyLeaveConfirm, initGlobalLinkActions, initHeadNavbarContentToggle, - initGlobalPopups, + initGlobalTooltips, } from './features/common-global.js'; import {initRepoTopicBar} from './features/repo-home.js'; import {initAdminEmails} from './features/admin-emails.js'; @@ -100,7 +100,7 @@ initVueEnv(); $(document).ready(() => { initGlobalCommon(); - initGlobalPopups(); + initGlobalTooltips(); initGlobalButtonClickOnEnter(); initGlobalButtons(); initGlobalCopyToClipboardListener(); diff --git a/web_src/js/modules/tippy.js b/web_src/js/modules/tippy.js index 6fd466cd9229..87f9e8a4b062 100644 --- a/web_src/js/modules/tippy.js +++ b/web_src/js/modules/tippy.js @@ -1,12 +1,56 @@ import tippy from 'tippy.js'; -export function createTippy(target, opts) { - return tippy(target, { +export function createTippy(target, opts = {}) { + const instance = tippy(target, { appendTo: document.body, placement: 'top-start', animation: false, allowHTML: true, + maxWidth: 500, // increase over default 350px arrow: ``, + ...(opts?.role && {theme: opts.role}), ...opts, }); + + // for popups where content refers to a DOM element, we use the 'hide' class to initially hide + // the content, now we can remove it as the content has been removed from the DOM by tippy + if (opts.content instanceof Element) { + opts.content.classList.remove('hide'); + } + + return instance; +} + +export function initTooltip(el, props = {}) { + const content = el.getAttribute('data-content') || props.content; + if (!content) return null; + return createTippy(el, { + content, + delay: 100, + role: 'tooltip', + ...props, + }); +} + +export function showTemporaryTooltip(target, content) { + let tippy, oldContent; + if (target._tippy) { + tippy = target._tippy; + oldContent = tippy.props.content; + } else { + tippy = initTooltip(target, {content}); + } + + tippy.setContent(content); + tippy.show(); + tippy.setProps({ + onHidden: (tippy) => { + if (oldContent) { + tippy.setContent(oldContent); + } else { + tippy.destroy(); + } + tippy.setProps({onHidden: undefined}); + }, + }); } diff --git a/web_src/less/_base.less b/web_src/less/_base.less index dc518eea951a..f2711c4482c9 100644 --- a/web_src/less/_base.less +++ b/web_src/less/_base.less @@ -155,6 +155,8 @@ --color-caret: var(--color-text-dark); --color-reaction-bg: #0000000a; --color-reaction-active-bg: var(--color-primary-alpha-20); + --color-tooltip-bg: #000000f0; + --color-tooltip-text: #ffffff; /* backgrounds */ --checkbox-mask-checked: url('data:image/svg+xml;utf8,'); --checkbox-mask-indeterminate: url('data:image/svg+xml;utf8,'); @@ -1313,7 +1315,7 @@ footer { } .hide { - display: none; + display: none !important; &.show-outdated { display: none !important; @@ -1873,41 +1875,6 @@ a.ui.basic.label:hover { color: #f05133; /* from https://upload.wikimedia.org/wikipedia/commons/e/e0/Git-logo.svg */ } -.ui.popup { - background-color: var(--color-body); - color: var(--color-secondary-dark-6); - border-color: var(--color-secondary); -} - -.ui.popup::before { - box-shadow: 1px 1px 0 0 var(--color-secondary); -} - -.ui.bottom.popup::before, -.ui.top.popup::before, -.ui.right.center.popup::before, -.ui.left.center.popup::before { - background-color: var(--color-body); -} - -.ui.bottom.left.popup::before, -.ui.bottom.right.popup::before, -.ui.bottom.center.popup::before { - box-shadow: -1px -1px 0 0 var(--color-secondary); -} - -.ui.left.center.popup::before { - box-shadow: 1px -1px 0 0 var(--color-secondary); -} - -.ui.right.center.popup::before { - box-shadow: -1px 1px 0 0 var(--color-secondary); -} - -.ui.popup .ui.label { - margin-bottom: .4em; -} - .color-icon { display: inline-block; border-radius: 100%; diff --git a/web_src/less/modules/tippy.less b/web_src/less/modules/tippy.less index aa2aed6ce211..1fcd0372ce5a 100644 --- a/web_src/less/modules/tippy.less +++ b/web_src/less/modules/tippy.less @@ -1,9 +1,5 @@ /* styles are based on node_modules/tippy.js/dist/tippy.css */ -.tippy-box[data-animation="fade"][data-state="hidden"] { - opacity: 0; -} - [data-tippy-root] { max-width: calc(100vw - 10px); } @@ -15,7 +11,21 @@ border: 1px solid var(--color-secondary); border-radius: var(--border-radius); font-size: 1rem; - transition-property: transform, visibility, opacity; +} + +.tippy-box[data-theme="tooltip"] { + background-color: var(--color-tooltip-bg); + color: var(--color-tooltip-text); + border: none; +} + +.tippy-box[data-theme="menu"] { + background-color: none; + color: var(--color-tooltip-text); +} + +.tippy-box[data-theme="menu"] .ui.menu { + border: none; } .tippy-content { @@ -24,6 +34,14 @@ z-index: 1; } +.tippy-box[data-theme="tooltip"] .tippy-content { + padding: .5rem 1rem; +} + +.tippy-box[data-theme="menu"] .tippy-content { + padding: 0; +} + .tippy-box[data-placement^="top"] > .tippy-svg-arrow { bottom: 0; } @@ -82,3 +100,12 @@ .tippy-svg-arrow-inner { fill: var(--color-body); } + +.tippy-box[data-theme="tooltip"] .tippy-svg-arrow-inner, +.tippy-box[data-theme="tooltip"] .tippy-svg-arrow-outer { + fill: var(--color-tooltip-bg); +} + +.tippy-box[data-theme="menu"] .tippy-svg-arrow-inner { + fill: var(--color-menu); +} diff --git a/web_src/svg/gitea-exclamation.svg b/web_src/svg/gitea-exclamation.svg new file mode 100644 index 000000000000..c83cb4158efc --- /dev/null +++ b/web_src/svg/gitea-exclamation.svg @@ -0,0 +1 @@ + \ No newline at end of file