+ {{$statusUnread := 1}}{{$statusRead := 2}}{{$statusPinned := 3}} {{$notificationUnreadCount := call .PageGlobalData.GetNotificationUnreadCount}} -
+ {{$pageTypeIsRead := eq $.PageType "read"}} +
- {{if and (eq .Status 1)}} + {{if and (not $pageTypeIsRead) $notificationUnreadCount}} {{$.CsrfTokenHtml}} -
- -
+ {{end}}
-
-
- {{if not .Notifications}} -
- {{svg "octicon-inbox" 56 "tw-mb-4"}} - {{if eq .Status 1}} - {{ctx.Locale.Tr "notification.no_unread"}} +
+ {{range $one := .Notifications}} +
+
+ {{if $one.Issue}} + {{template "shared/issueicon" $one.Issue}} {{else}} - {{ctx.Locale.Tr "notification.no_read"}} + {{svg "octicon-repo" 16 "text grey"}} {{end}}
- {{else}} - {{range $notification := .Notifications}} -
-
- {{if .Issue}} - {{template "shared/issueicon" .Issue}} - {{else}} - {{svg "octicon-repo" 16 "text grey"}} - {{end}} -
- -
- {{.Repository.FullName}} {{if .Issue}}#{{.Issue.Index}}{{end}} - {{if eq .Status 3}} - {{svg "octicon-pin" 13 "text blue tw-mt-0.5 tw-ml-1"}} - {{end}} -
-
- - {{if .Issue}} - {{.Issue.Title | ctx.RenderUtils.RenderIssueSimpleTitle}} - {{else}} - {{.Repository.FullName}} - {{end}} - -
-
-
- {{if .Issue}} - {{DateUtils.TimeSince .Issue.UpdatedUnix}} - {{else}} - {{DateUtils.TimeSince .UpdatedUnix}} - {{end}} -
-
- {{if ne .Status 3}} -
- {{$.CsrfTokenHtml}} - - - -
- {{end}} - {{if or (eq .Status 1) (eq .Status 3)}} -
- {{$.CsrfTokenHtml}} - - - - -
- {{else if eq .Status 2}} -
- {{$.CsrfTokenHtml}} - - - - -
- {{end}} -
+ +
+ {{$one.Repository.FullName}} {{if $one.Issue}}#{{$one.Issue.Index}}{{end}} + {{if eq $one.Status $statusPinned}} + {{svg "octicon-pin" 13 "text blue"}} + {{end}}
+
+ {{if $one.Issue}} + {{$one.Issue.Title | ctx.RenderUtils.RenderIssueSimpleTitle}} + {{else}} + {{$one.Repository.FullName}} + {{end}} +
+
+
+ {{if $one.Issue}} + {{DateUtils.TimeSince $one.Issue.UpdatedUnix}} + {{else}} + {{DateUtils.TimeSince $one.UpdatedUnix}} + {{end}} +
+
+ {{$.CsrfTokenHtml}} + + {{if ne $one.Status $statusPinned}} + + {{end}} + {{if or (eq $one.Status $statusUnread) (eq $one.Status $statusPinned)}} + + {{else if eq $one.Status $statusRead}} + + {{end}} +
+
+ {{else}} +
+ {{svg "octicon-inbox" 56 "tw-mb-4"}} + {{if $pageTypeIsRead}} + {{ctx.Locale.Tr "notification.no_read"}} + {{else}} + {{ctx.Locale.Tr "notification.no_unread"}} {{end}} - {{end}} -
+
+ {{end}}
{{template "base/paginate" .}}
diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl index 2c01c88d47c..7dbac0ebd45 100644 --- a/templates/user/settings/account.tmpl +++ b/templates/user/settings/account.tmpl @@ -35,37 +35,12 @@ {{end}}
- {{if not (and ($.UserDisabledFeatures.Contains "manage_credentials") (not $.EnableNotifyMail))}} + {{if not ($.UserDisabledFeatures.Contains "manage_credentials")}}

{{ctx.Locale.Tr "settings.manage_emails"}}

- {{if $.EnableNotifyMail}} -
-
- {{$.CsrfTokenHtml}} - -
- - -
-
- -
-
-
- {{end}} {{if not ($.UserDisabledFeatures.Contains "manage_credentials")}} {{range .Emails}}
diff --git a/templates/user/settings/navbar.tmpl b/templates/user/settings/navbar.tmpl index c6c15512abd..34e089a68a9 100644 --- a/templates/user/settings/navbar.tmpl +++ b/templates/user/settings/navbar.tmpl @@ -4,11 +4,16 @@ {{ctx.Locale.Tr "settings.profile"}} - {{if not (and ($.UserDisabledFeatures.Contains "manage_credentials" "deletion") (not $.EnableNotifyMail))}} + {{if not ($.UserDisabledFeatures.Contains "manage_credentials" "deletion")}} {{ctx.Locale.Tr "settings.account"}} {{end}} + {{if $.EnableNotifyMail}} + + {{ctx.Locale.Tr "notifications"}} + + {{end}} {{ctx.Locale.Tr "settings.appearance"}} diff --git a/templates/user/settings/notifications.tmpl b/templates/user/settings/notifications.tmpl new file mode 100644 index 00000000000..40094aab4ca --- /dev/null +++ b/templates/user/settings/notifications.tmpl @@ -0,0 +1,65 @@ +{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings")}} +
+

+ {{ctx.Locale.Tr "notifications"}} +

+
+
+
+
+ {{$.CsrfTokenHtml}} +
+ + +
+
+ +
+
+
+
+
+ + {{if .EnableActions}} +

+ {{ctx.Locale.Tr "actions.actions"}} +

+
+
+
+
+ {{$.CsrfTokenHtml}} +
+ + +
+
+ +
+
+
+
+
+ {{end}} +
+ +{{template "user/settings/layout_footer" .}} diff --git a/tests/integration/api_packages_generic_test.go b/tests/integration/api_packages_generic_test.go index 5f410fc4704..94e2c6072ce 100644 --- a/tests/integration/api_packages_generic_test.go +++ b/tests/integration/api_packages_generic_test.go @@ -141,37 +141,25 @@ func TestPackageGeneric(t *testing.T) { t.Run("ServeDirect", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - if setting.Packages.Storage.Type != setting.MinioStorageType && setting.Packages.Storage.Type != setting.AzureBlobStorageType { - t.Skip("Test skipped for non-Minio-storage and non-AzureBlob-storage.") - return - } - if setting.Packages.Storage.Type == setting.MinioStorageType { - if !setting.Packages.Storage.MinioConfig.ServeDirect { - old := setting.Packages.Storage.MinioConfig.ServeDirect - defer func() { - setting.Packages.Storage.MinioConfig.ServeDirect = old - }() - - setting.Packages.Storage.MinioConfig.ServeDirect = true - } + defer test.MockVariableValue(&setting.Packages.Storage.MinioConfig.ServeDirect, true)() } else if setting.Packages.Storage.Type == setting.AzureBlobStorageType { - if !setting.Packages.Storage.AzureBlobConfig.ServeDirect { - old := setting.Packages.Storage.AzureBlobConfig.ServeDirect - defer func() { - setting.Packages.Storage.AzureBlobConfig.ServeDirect = old - }() - - setting.Packages.Storage.AzureBlobConfig.ServeDirect = true - } + defer test.MockVariableValue(&setting.Packages.Storage.AzureBlobConfig.ServeDirect, true)() + } else { + t.Skip("Test skipped for non-Minio-storage and non-AzureBlob-storage.") } - req := NewRequest(t, "GET", url+"/"+filename) - resp := MakeRequest(t, req, http.StatusSeeOther) + req = NewRequest(t, "HEAD", url+"/"+filename) + resp = MakeRequest(t, req, http.StatusSeeOther) + location := resp.Header().Get("Location") + assert.NotEmpty(t, location) + checkDownloadCount(2) + req = NewRequest(t, "GET", url+"/"+filename) + resp = MakeRequest(t, req, http.StatusSeeOther) checkDownloadCount(3) - location := resp.Header().Get("Location") + location = resp.Header().Get("Location") assert.NotEmpty(t, location) resp2, err := (&http.Client{}).Get(location) diff --git a/tests/integration/git_misc_test.go b/tests/integration/git_misc_test.go index a5c53fd6e96..d5a4af07bab 100644 --- a/tests/integration/git_misc_test.go +++ b/tests/integration/git_misc_test.go @@ -217,7 +217,7 @@ func TestAgitReviewStaleness(t *testing.T) { assert.NoError(t, err) defer baseGitRepo.Close() - updatedCommitID, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName()) + updatedCommitID, err := baseGitRepo.GetRefCommitID(pr.GetGitHeadRefName()) assert.NoError(t, err) t.Logf("Updated commit ID: %s", updatedCommitID) diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index f8bc33c32aa..a2247801f76 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -9,9 +9,11 @@ import ( "fmt" "io" "net/http" + "net/http/httptest" "strings" "testing" + asymkey_model "code.gitea.io/gitea/models/asymkey" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" @@ -20,9 +22,13 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/oauth2_provider" "code.gitea.io/gitea/tests" + "github.com/markbates/goth" + "github.com/markbates/goth/gothic" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -931,3 +937,107 @@ func testOAuth2WellKnown(t *testing.T) { defer test.MockVariableValue(&setting.OAuth2.Enabled, false)() MakeRequest(t, NewRequest(t, "GET", urlOpenidConfiguration), http.StatusNotFound) } + +func addOAuth2Source(t *testing.T, authName string, cfg oauth2.Source) { + cfg.Provider = util.IfZero(cfg.Provider, "gitea") + err := auth_model.CreateSource(db.DefaultContext, &auth_model.Source{ + Type: auth_model.OAuth2, + Name: authName, + IsActive: true, + Cfg: &cfg, + }) + require.NoError(t, err) +} + +func TestSignInOauthCallbackSyncSSHKeys(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + var mockServer *httptest.Server + mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/.well-known/openid-configuration": + _, _ = w.Write([]byte(`{ + "issuer": "` + mockServer.URL + `", + "authorization_endpoint": "` + mockServer.URL + `/authorize", + "token_endpoint": "` + mockServer.URL + `/token", + "userinfo_endpoint": "` + mockServer.URL + `/userinfo" + }`)) + default: + http.NotFound(w, r) + } + })) + defer mockServer.Close() + + ctx := t.Context() + oauth2Source := oauth2.Source{ + Provider: "openidConnect", + ClientID: "test-client-id", + SSHPublicKeyClaimName: "sshpubkey", + FullNameClaimName: "name", + OpenIDConnectAutoDiscoveryURL: mockServer.URL + "/.well-known/openid-configuration", + } + addOAuth2Source(t, "test-oidc-source", oauth2Source) + authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(ctx, "test-oidc-source") + require.NoError(t, err) + + sshKey1 := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf" + sshKey2 := "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo=" + sshKey3 := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEHjnNEfE88W1pvBLdV3otv28x760gdmPao3lVD5uAt9" + cases := []struct { + testName string + mockFullName string + mockRawData map[string]any + expectedSSHPubKeys []string + }{ + { + testName: "Login1", + mockFullName: "FullName1", + mockRawData: map[string]any{"sshpubkey": []any{sshKey1 + " any-comment"}}, + expectedSSHPubKeys: []string{sshKey1}, + }, + { + testName: "Login2", + mockFullName: "FullName2", + mockRawData: map[string]any{"sshpubkey": []any{sshKey2 + " any-comment", sshKey3}}, + expectedSSHPubKeys: []string{sshKey2, sshKey3}, + }, + { + testName: "Login3", + mockFullName: "FullName3", + mockRawData: map[string]any{}, + expectedSSHPubKeys: []string{}, + }, + } + + session := emptyTestSession(t) + for _, c := range cases { + t.Run(c.testName, func(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2Client.Username, "")() + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + defer test.MockVariableValue(&gothic.CompleteUserAuth, func(res http.ResponseWriter, req *http.Request) (goth.User, error) { + return goth.User{ + Provider: authSource.Cfg.(*oauth2.Source).Provider, + UserID: "oidc-userid", + Email: "oidc-email@example.com", + RawData: c.mockRawData, + Name: c.mockFullName, + }, nil + })() + req := NewRequest(t, "GET", "/user/oauth2/test-oidc-source/callback?code=XYZ&state=XYZ") + session.MakeRequest(t, req, http.StatusSeeOther) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "oidc-userid"}) + keys, _, err := db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{ + ListOptions: db.ListOptionsAll, + OwnerID: user.ID, + LoginSourceID: authSource.ID, + }) + require.NoError(t, err) + var sshPubKeys []string + for _, key := range keys { + sshPubKeys = append(sshPubKeys, key.Content) + } + assert.ElementsMatch(t, c.expectedSSHPubKeys, sshPubKeys) + assert.Equal(t, c.mockFullName, user.FullName) + }) + } +} diff --git a/tests/integration/pull_create_test.go b/tests/integration/pull_create_test.go index 179c84e6739..44ef5019611 100644 --- a/tests/integration/pull_create_test.go +++ b/tests/integration/pull_create_test.go @@ -163,10 +163,10 @@ func TestPullCreate_TitleEscape(t *testing.T) { req = NewRequest(t, "GET", url) resp = session.MakeRequest(t, req, http.StatusOK) htmlDoc = NewHTMLParser(t, resp.Body) - titleHTML, err := htmlDoc.doc.Find(".comment-list .timeline-item.event .text b").First().Html() + titleHTML, err := htmlDoc.doc.Find(".comment-list .timeline-item.event .comment-text-line b").First().Html() assert.NoError(t, err) assert.Equal(t, "<i>XSS PR</i>", titleHTML) - titleHTML, err = htmlDoc.doc.Find(".comment-list .timeline-item.event .text b").Next().Html() + titleHTML, err = htmlDoc.doc.Find(".comment-list .timeline-item.event .comment-text-line b").Next().Html() assert.NoError(t, err) assert.Equal(t, "<u>XSS PR</u>", titleHTML) }) diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 897a78cef45..3afa5f10f18 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -768,7 +768,7 @@ func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) { // update commit status to success, then it should be merged automatically baseGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, baseRepo) assert.NoError(t, err) - sha, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName()) + sha, err := baseGitRepo.GetRefCommitID(pr.GetGitHeadRefName()) assert.NoError(t, err) masterCommitID, err := baseGitRepo.GetBranchCommitID("master") assert.NoError(t, err) @@ -850,7 +850,7 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApproval(t *testing.T) { // update commit status to success, then it should be merged automatically baseGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, baseRepo) assert.NoError(t, err) - sha, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName()) + sha, err := baseGitRepo.GetRefCommitID(pr.GetGitHeadRefName()) assert.NoError(t, err) masterCommitID, err := baseGitRepo.GetBranchCommitID("master") assert.NoError(t, err) @@ -979,7 +979,7 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing. // update commit status to success, then it should be merged automatically baseGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, baseRepo) assert.NoError(t, err) - sha, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName()) + sha, err := baseGitRepo.GetRefCommitID(pr.GetGitHeadRefName()) assert.NoError(t, err) masterCommitID, err := baseGitRepo.GetBranchCommitID("master") assert.NoError(t, err) diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go index 0097a7f62eb..b8f086e2b15 100644 --- a/tests/integration/repo_commits_test.go +++ b/tests/integration/repo_commits_test.go @@ -24,39 +24,59 @@ import ( func TestRepoCommits(t *testing.T) { defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") - // Request repository commits page - req := NewRequest(t, "GET", "/user2/repo1/commits/branch/master") - resp := session.MakeRequest(t, req, http.StatusOK) + t.Run("CommitList", func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo16/commits/branch/master") + resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - commitURL, exists := doc.doc.Find("#commits-table .commit-id-short").Attr("href") - assert.True(t, exists) - assert.NotEmpty(t, commitURL) -} - -func Test_ReposGitCommitListNotMaster(t *testing.T) { - defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user2/repo16/commits/branch/master") - resp := session.MakeRequest(t, req, http.StatusOK) - - doc := NewHTMLParser(t, resp.Body) - var commits []string - doc.doc.Find("#commits-table .commit-id-short").Each(func(i int, s *goquery.Selection) { - commitURL, _ := s.Attr("href") - commits = append(commits, path.Base(commitURL)) + var commits, userHrefs []string + doc := NewHTMLParser(t, resp.Body) + doc.doc.Find("#commits-table .commit-id-short").Each(func(i int, s *goquery.Selection) { + commits = append(commits, path.Base(s.AttrOr("href", ""))) + }) + doc.doc.Find("#commits-table .author-wrapper").Each(func(i int, s *goquery.Selection) { + userHrefs = append(userHrefs, s.AttrOr("href", "")) + }) + assert.Equal(t, []string{"69554a64c1e6030f051e5c3f94bfbd773cd6a324", "27566bd5738fc8b4e3fef3c5e72cce608537bd95", "5099b81332712fe655e34e8dd63574f503f61811"}, commits) + assert.Equal(t, []string{"/user2", "/user21", "/user2"}, userHrefs) }) - assert.Equal(t, []string{"69554a64c1e6030f051e5c3f94bfbd773cd6a324", "27566bd5738fc8b4e3fef3c5e72cce608537bd95", "5099b81332712fe655e34e8dd63574f503f61811"}, commits) - var userHrefs []string - doc.doc.Find("#commits-table .author-wrapper").Each(func(i int, s *goquery.Selection) { - userHref, _ := s.Attr("href") - userHrefs = append(userHrefs, userHref) + t.Run("LastCommit", func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo16") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + commitHref := doc.doc.Find(".latest-commit .commit-id-short").AttrOr("href", "") + authorHref := doc.doc.Find(".latest-commit .author-wrapper").AttrOr("href", "") + assert.Equal(t, "/user2/repo16/commit/69554a64c1e6030f051e5c3f94bfbd773cd6a324", commitHref) + assert.Equal(t, "/user2", authorHref) + }) + + t.Run("CommitListNonExistingCommiter", func(t *testing.T) { + // check the commit list for a repository with no gitea user + // * commit 985f0301dba5e7b34be866819cd15ad3d8f508ee (branch2) + // * Author: 6543 <6543@obermui.de> + req := NewRequest(t, "GET", "/user2/repo1/commits/branch/branch2") + resp := session.MakeRequest(t, req, http.StatusOK) + + doc := NewHTMLParser(t, resp.Body) + commitHref := doc.doc.Find("#commits-table tr:first-child .commit-id-short").AttrOr("href", "") + assert.Equal(t, "/user2/repo1/commit/985f0301dba5e7b34be866819cd15ad3d8f508ee", commitHref) + authorElem := doc.doc.Find("#commits-table tr:first-child .author-wrapper") + assert.Equal(t, "6543", authorElem.Text()) + assert.Equal(t, "span", authorElem.Nodes[0].Data) + }) + + t.Run("LastCommitNonExistingCommiter", func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo1/src/branch/branch2") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + commitHref := doc.doc.Find(".latest-commit .commit-id-short").AttrOr("href", "") + assert.Equal(t, "/user2/repo1/commit/985f0301dba5e7b34be866819cd15ad3d8f508ee", commitHref) + authorElem := doc.doc.Find(".latest-commit .author-wrapper") + assert.Equal(t, "6543", authorElem.Text()) + assert.Equal(t, "span", authorElem.Nodes[0].Data) }) - assert.Equal(t, []string{"/user2", "/user21", "/user2"}, userHrefs) } func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) { diff --git a/tests/integration/signin_test.go b/tests/integration/signin_test.go index 67af5b5877f..fa37145d98c 100644 --- a/tests/integration/signin_test.go +++ b/tests/integration/signin_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -17,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers" + "code.gitea.io/gitea/routers/web/auth" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/tests" @@ -103,8 +105,9 @@ func TestEnablePasswordSignInFormAndEnablePasskeyAuth(t *testing.T) { defer tests.PrepareTestEnv(t)() mockLinkAccount := func(ctx *context.Context) { + authSource := auth_model.Source{ID: 1} gothUser := goth.User{Email: "invalid-email", Name: "."} - _ = ctx.Session.Set("linkAccountGothUser", gothUser) + _ = auth.Oauth2SetLinkAccountData(ctx, auth.LinkAccountData{AuthSourceID: authSource.ID, GothUser: gothUser}) } t.Run("EnablePasswordSignInForm=false", func(t *testing.T) { diff --git a/tests/integration/timetracking_test.go b/tests/integration/timetracking_test.go index 4e8109be962..ebe084ccdce 100644 --- a/tests/integration/timetracking_test.go +++ b/tests/integration/timetracking_test.go @@ -56,7 +56,7 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo resp = session.MakeRequest(t, req, http.StatusOK) htmlDoc = NewHTMLParser(t, resp.Body) - events := htmlDoc.doc.Find(".event > span.text") + events := htmlDoc.doc.Find(".event > .comment-text-line") assert.Contains(t, events.Last().Text(), "started working") AssertHTMLElement(t, htmlDoc, ".issue-stop-time", true) @@ -74,7 +74,7 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo resp = session.MakeRequest(t, req, http.StatusOK) htmlDoc = NewHTMLParser(t, resp.Body) - events = htmlDoc.doc.Find(".event > span.text") + events = htmlDoc.doc.Find(".event > .comment-text-line") assert.Contains(t, events.Last().Text(), "worked for ") } else { session.MakeRequest(t, reqStart, http.StatusNotFound) diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000000..990c98a3130 --- /dev/null +++ b/uv.lock @@ -0,0 +1,330 @@ +version = 1 +revision = 2 +requires-python = ">=3.10" + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cssbeautifier" +version = "1.15.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "editorconfig" }, + { name = "jsbeautifier" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/01/fdf41c1e5f93d359681976ba10410a04b299d248e28ecce1d4e88588dde4/cssbeautifier-1.15.4.tar.gz", hash = "sha256:9bb08dc3f64c101a01677f128acf01905914cf406baf87434dcde05b74c0acf5", size = 25376, upload-time = "2025-02-27T17:53:51.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/51/ef6c5628e46092f0a54c7cee69acc827adc6b6aab57b55d344fefbdf28f1/cssbeautifier-1.15.4-py3-none-any.whl", hash = "sha256:78c84d5e5378df7d08622bbd0477a1abdbd209680e95480bf22f12d5701efc98", size = 123667, upload-time = "2025-02-27T17:53:43.594Z" }, +] + +[[package]] +name = "djlint" +version = "1.36.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama" }, + { name = "cssbeautifier" }, + { name = "jsbeautifier" }, + { name = "json5" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tqdm" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/89/ecf5be9f5c59a0c53bcaa29671742c5e269cc7d0e2622e3f65f41df251bf/djlint-1.36.4.tar.gz", hash = "sha256:17254f218b46fe5a714b224c85074c099bcb74e3b2e1f15c2ddc2cf415a408a1", size = 47849, upload-time = "2024-12-24T13:06:36.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/71/6a3ce2b49a62e635b85dce30ccf3eb3a18fe79275d45535325a55a63d3a3/djlint-1.36.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2dfb60883ceb92465201bfd392291a7597c6752baede6fbb6f1980cac8d6c5c", size = 354135, upload-time = "2024-12-24T13:05:49.732Z" }, + { url = "https://files.pythonhosted.org/packages/72/47/308412dc579e277c910774f41b380308d582862b16763425583e69e0fc14/djlint-1.36.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4bc6a1320c0030244b530ac200642f883d3daa451a115920ef3d56d08b644292", size = 328501, upload-time = "2024-12-24T13:05:53.861Z" }, + { url = "https://files.pythonhosted.org/packages/9b/6f/428dc044d1e34363265b1301dc9b53253007acd858879d54b369d233aa96/djlint-1.36.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3164a048c7bb0baf042387b1e33f9bbbf99d90d1337bb4c3d66eb0f96f5400a1", size = 415849, upload-time = "2024-12-24T13:05:56.377Z" }, + { url = "https://files.pythonhosted.org/packages/d6/13/0d488e551d73ddf369552fc6f4c7702ea683e4bc1305bcf5c1d198fbdace/djlint-1.36.4-cp310-cp310-win_amd64.whl", hash = "sha256:3196d5277da5934962d67ad6c33a948ba77a7b6eadf064648bef6ee5f216b03c", size = 360969, upload-time = "2024-12-24T13:05:59.582Z" }, + { url = "https://files.pythonhosted.org/packages/04/68/18ecd1e4d54a523e1d077f01419d669116e5dede97f97f1eb8ddb918a872/djlint-1.36.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d68da0ed10ee9ca1e32e225cbb8e9b98bf7e6f8b48a8e4836117b6605b88cc7", size = 344261, upload-time = "2024-12-24T13:06:01.136Z" }, + { url = "https://files.pythonhosted.org/packages/1e/03/005cf5c66e57ca2d26249f8385bc64420b2a95fea81c5eb619c925199029/djlint-1.36.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c0478d5392247f1e6ee29220bbdbf7fb4e1bc0e7e83d291fda6fb926c1787ba7", size = 319580, upload-time = "2024-12-24T13:06:03.824Z" }, + { url = "https://files.pythonhosted.org/packages/9f/88/aea3c81343a273a87362f30442abc13351dc8ada0b10e51daa285b4dddac/djlint-1.36.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:962f7b83aee166e499eff916d631c6dde7f1447d7610785a60ed2a75a5763483", size = 407070, upload-time = "2024-12-24T13:06:05.356Z" }, + { url = "https://files.pythonhosted.org/packages/60/77/0f767ac0b72e9a664bb8c92b8940f21bc1b1e806e5bd727584d40a4ca551/djlint-1.36.4-cp311-cp311-win_amd64.whl", hash = "sha256:53cbc450aa425c832f09bc453b8a94a039d147b096740df54a3547fada77ed08", size = 360775, upload-time = "2024-12-24T13:06:10.176Z" }, + { url = "https://files.pythonhosted.org/packages/53/f5/9ae02b875604755d4d00cebf96b218b0faa3198edc630f56a139581aed87/djlint-1.36.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff9faffd7d43ac20467493fa71d5355b5b330a00ade1c4d1e859022f4195223b", size = 354886, upload-time = "2024-12-24T13:06:11.571Z" }, + { url = "https://files.pythonhosted.org/packages/97/51/284443ff2f2a278f61d4ae6ae55eaf820ad9f0fd386d781cdfe91f4de495/djlint-1.36.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:79489e262b5ac23a8dfb7ca37f1eea979674cfc2d2644f7061d95bea12c38f7e", size = 323237, upload-time = "2024-12-24T13:06:13.057Z" }, + { url = "https://files.pythonhosted.org/packages/6d/5e/791f4c5571f3f168ad26fa3757af8f7a05c623fde1134a9c4de814ee33b7/djlint-1.36.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e58c5fa8c6477144a0be0a87273706a059e6dd0d6efae01146ae8c29cdfca675", size = 411719, upload-time = "2024-12-24T13:06:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/1f/11/894425add6f84deffcc6e373f2ce250f2f7b01aa58c7f230016ebe7a0085/djlint-1.36.4-cp312-cp312-win_amd64.whl", hash = "sha256:bb6903777bf3124f5efedcddf1f4716aef097a7ec4223fc0fa54b865829a6e08", size = 362076, upload-time = "2024-12-24T13:06:17.517Z" }, + { url = "https://files.pythonhosted.org/packages/da/83/88b4c885812921739f5529a29085c3762705154d41caf7eb9a8886a3380c/djlint-1.36.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ead475013bcac46095b1bbc8cf97ed2f06e83422335734363f8a76b4ba7e47c2", size = 354384, upload-time = "2024-12-24T13:06:20.809Z" }, + { url = "https://files.pythonhosted.org/packages/32/38/67695f7a150b3d9d62fadb65242213d96024151570c3cf5d966effa68b0e/djlint-1.36.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6c601dfa68ea253311deb4a29a7362b7a64933bdfcfb5a06618f3e70ad1fa835", size = 322971, upload-time = "2024-12-24T13:06:22.185Z" }, + { url = "https://files.pythonhosted.org/packages/ac/7a/cd851393291b12e7fe17cf5d4d8874b8ea133aebbe9235f5314aabc96a52/djlint-1.36.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bda5014f295002363381969864addeb2db13955f1b26e772657c3b273ed7809f", size = 410972, upload-time = "2024-12-24T13:06:24.077Z" }, + { url = "https://files.pythonhosted.org/packages/6c/31/56469120394b970d4f079a552fde21ed27702ca729595ab0ed459eb6d240/djlint-1.36.4-cp313-cp313-win_amd64.whl", hash = "sha256:16ce37e085afe5a30953b2bd87cbe34c37843d94c701fc68a2dda06c1e428ff4", size = 362053, upload-time = "2024-12-24T13:06:25.432Z" }, + { url = "https://files.pythonhosted.org/packages/4b/67/f7aeea9be6fb3bd984487af8d0d80225a0b1e5f6f7126e3332d349fb13fe/djlint-1.36.4-py3-none-any.whl", hash = "sha256:e9699b8ac3057a6ed04fb90835b89bee954ed1959c01541ce4f8f729c938afdd", size = 52290, upload-time = "2024-12-24T13:06:33.76Z" }, +] + +[[package]] +name = "editorconfig" +version = "0.17.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/3a/a61d9a1f319a186b05d14df17daea42fcddea63c213bcd61a929fb3a6796/editorconfig-0.17.1.tar.gz", hash = "sha256:23c08b00e8e08cc3adcddb825251c497478df1dada6aefeb01e626ad37303745", size = 14695, upload-time = "2025-06-09T08:21:37.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/fd/a40c621ff207f3ce8e484aa0fc8ba4eb6e3ecf52e15b42ba764b457a9550/editorconfig-0.17.1-py3-none-any.whl", hash = "sha256:1eda9c2c0db8c16dbd50111b710572a5e6de934e39772de1959d41f64fc17c82", size = 16360, upload-time = "2025-06-09T08:21:35.654Z" }, +] + +[[package]] +name = "gitea" +version = "0.0.0" +source = { virtual = "." } + +[package.dev-dependencies] +dev = [ + { name = "djlint" }, + { name = "yamllint" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "djlint", specifier = "==1.36.4" }, + { name = "yamllint", specifier = "==1.37.1" }, +] + +[[package]] +name = "jsbeautifier" +version = "1.15.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "editorconfig" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/98/d6cadf4d5a1c03b2136837a435682418c29fdeb66be137128544cecc5b7a/jsbeautifier-1.15.4.tar.gz", hash = "sha256:5bb18d9efb9331d825735fbc5360ee8f1aac5e52780042803943aa7f854f7592", size = 75257, upload-time = "2025-02-27T17:53:53.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/14/1c65fccf8413d5f5c6e8425f84675169654395098000d8bddc4e9d3390e1/jsbeautifier-1.15.4-py3-none-any.whl", hash = "sha256:72f65de312a3f10900d7685557f84cb61a9733c50dcc27271a39f5b0051bf528", size = 94707, upload-time = "2025-02-27T17:53:46.152Z" }, +] + +[[package]] +name = "json5" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/be/c6c745ec4c4539b25a278b70e29793f10382947df0d9efba2fa09120895d/json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a", size = 51907, upload-time = "2025-04-03T16:33:13.201Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/9f/3500910d5a98549e3098807493851eeef2b89cdd3032227558a104dfe926/json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db", size = 36079, upload-time = "2025-04-03T16:33:11.927Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "regex" +version = "2024.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494, upload-time = "2024-11-06T20:12:31.635Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/3c/4651f6b130c6842a8f3df82461a8950f923925db8b6961063e82744bddcc/regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91", size = 482674, upload-time = "2024-11-06T20:08:57.575Z" }, + { url = "https://files.pythonhosted.org/packages/15/51/9f35d12da8434b489c7b7bffc205c474a0a9432a889457026e9bc06a297a/regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0", size = 287684, upload-time = "2024-11-06T20:08:59.787Z" }, + { url = "https://files.pythonhosted.org/packages/bd/18/b731f5510d1b8fb63c6b6d3484bfa9a59b84cc578ac8b5172970e05ae07c/regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e", size = 284589, upload-time = "2024-11-06T20:09:01.896Z" }, + { url = "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde", size = 782511, upload-time = "2024-11-06T20:09:04.062Z" }, + { url = "https://files.pythonhosted.org/packages/1b/2b/323e72d5d2fd8de0d9baa443e1ed70363ed7e7b2fb526f5950c5cb99c364/regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e", size = 821149, upload-time = "2024-11-06T20:09:06.237Z" }, + { url = "https://files.pythonhosted.org/packages/90/30/63373b9ea468fbef8a907fd273e5c329b8c9535fee36fc8dba5fecac475d/regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2", size = 809707, upload-time = "2024-11-06T20:09:07.715Z" }, + { url = "https://files.pythonhosted.org/packages/f2/98/26d3830875b53071f1f0ae6d547f1d98e964dd29ad35cbf94439120bb67a/regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf", size = 781702, upload-time = "2024-11-06T20:09:10.101Z" }, + { url = "https://files.pythonhosted.org/packages/87/55/eb2a068334274db86208ab9d5599ffa63631b9f0f67ed70ea7c82a69bbc8/regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c", size = 771976, upload-time = "2024-11-06T20:09:11.566Z" }, + { url = "https://files.pythonhosted.org/packages/74/c0/be707bcfe98254d8f9d2cff55d216e946f4ea48ad2fd8cf1428f8c5332ba/regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86", size = 697397, upload-time = "2024-11-06T20:09:13.119Z" }, + { url = "https://files.pythonhosted.org/packages/49/dc/bb45572ceb49e0f6509f7596e4ba7031f6819ecb26bc7610979af5a77f45/regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67", size = 768726, upload-time = "2024-11-06T20:09:14.85Z" }, + { url = "https://files.pythonhosted.org/packages/5a/db/f43fd75dc4c0c2d96d0881967897926942e935d700863666f3c844a72ce6/regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d", size = 775098, upload-time = "2024-11-06T20:09:16.504Z" }, + { url = "https://files.pythonhosted.org/packages/99/d7/f94154db29ab5a89d69ff893159b19ada89e76b915c1293e98603d39838c/regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2", size = 839325, upload-time = "2024-11-06T20:09:18.698Z" }, + { url = "https://files.pythonhosted.org/packages/f7/17/3cbfab1f23356fbbf07708220ab438a7efa1e0f34195bf857433f79f1788/regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008", size = 843277, upload-time = "2024-11-06T20:09:21.725Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f2/48b393b51900456155de3ad001900f94298965e1cad1c772b87f9cfea011/regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62", size = 773197, upload-time = "2024-11-06T20:09:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/45/3f/ef9589aba93e084cd3f8471fded352826dcae8489b650d0b9b27bc5bba8a/regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e", size = 261714, upload-time = "2024-11-06T20:09:26.36Z" }, + { url = "https://files.pythonhosted.org/packages/42/7e/5f1b92c8468290c465fd50c5318da64319133231415a8aa6ea5ab995a815/regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519", size = 274042, upload-time = "2024-11-06T20:09:28.762Z" }, + { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669, upload-time = "2024-11-06T20:09:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684, upload-time = "2024-11-06T20:09:32.915Z" }, + { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589, upload-time = "2024-11-06T20:09:35.504Z" }, + { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121, upload-time = "2024-11-06T20:09:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275, upload-time = "2024-11-06T20:09:40.371Z" }, + { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257, upload-time = "2024-11-06T20:09:43.059Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727, upload-time = "2024-11-06T20:09:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667, upload-time = "2024-11-06T20:09:49.828Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963, upload-time = "2024-11-06T20:09:51.819Z" }, + { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700, upload-time = "2024-11-06T20:09:53.982Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592, upload-time = "2024-11-06T20:09:56.222Z" }, + { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929, upload-time = "2024-11-06T20:09:58.642Z" }, + { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213, upload-time = "2024-11-06T20:10:00.867Z" }, + { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734, upload-time = "2024-11-06T20:10:03.361Z" }, + { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052, upload-time = "2024-11-06T20:10:05.179Z" }, + { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781, upload-time = "2024-11-06T20:10:07.07Z" }, + { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455, upload-time = "2024-11-06T20:10:09.117Z" }, + { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759, upload-time = "2024-11-06T20:10:11.155Z" }, + { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976, upload-time = "2024-11-06T20:10:13.24Z" }, + { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077, upload-time = "2024-11-06T20:10:15.37Z" }, + { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160, upload-time = "2024-11-06T20:10:19.027Z" }, + { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896, upload-time = "2024-11-06T20:10:21.85Z" }, + { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997, upload-time = "2024-11-06T20:10:24.329Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725, upload-time = "2024-11-06T20:10:28.067Z" }, + { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481, upload-time = "2024-11-06T20:10:31.612Z" }, + { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896, upload-time = "2024-11-06T20:10:34.054Z" }, + { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138, upload-time = "2024-11-06T20:10:36.142Z" }, + { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692, upload-time = "2024-11-06T20:10:38.394Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135, upload-time = "2024-11-06T20:10:40.367Z" }, + { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567, upload-time = "2024-11-06T20:10:43.467Z" }, + { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525, upload-time = "2024-11-06T20:10:45.19Z" }, + { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324, upload-time = "2024-11-06T20:10:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617, upload-time = "2024-11-06T20:10:49.312Z" }, + { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023, upload-time = "2024-11-06T20:10:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072, upload-time = "2024-11-06T20:10:52.926Z" }, + { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130, upload-time = "2024-11-06T20:10:54.828Z" }, + { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857, upload-time = "2024-11-06T20:10:56.634Z" }, + { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006, upload-time = "2024-11-06T20:10:59.369Z" }, + { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650, upload-time = "2024-11-06T20:11:02.042Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545, upload-time = "2024-11-06T20:11:03.933Z" }, + { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045, upload-time = "2024-11-06T20:11:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182, upload-time = "2024-11-06T20:11:09.06Z" }, + { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733, upload-time = "2024-11-06T20:11:11.256Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122, upload-time = "2024-11-06T20:11:13.161Z" }, + { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545, upload-time = "2024-11-06T20:11:15Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "yamllint" +version = "1.37.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pathspec" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/f2/cd8b7584a48ee83f0bc94f8a32fea38734cefcdc6f7324c4d3bfc699457b/yamllint-1.37.1.tar.gz", hash = "sha256:81f7c0c5559becc8049470d86046b36e96113637bcbe4753ecef06977c00245d", size = 141613, upload-time = "2025-05-04T08:25:54.355Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/b9/be7a4cfdf47e03785f657f94daea8123e838d817be76c684298305bd789f/yamllint-1.37.1-py3-none-any.whl", hash = "sha256:364f0d79e81409f591e323725e6a9f4504c8699ddf2d7263d8d2b539cd66a583", size = 68813, upload-time = "2025-05-04T08:25:52.552Z" }, +] diff --git a/web_src/css/base.css b/web_src/css/base.css index d358f2cac6d..6ff060f1f9e 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -38,7 +38,7 @@ --font-size-label: 12px; /* font size of individual labels */ --gap-inline: 0.25rem; /* gap for inline texts and elements, for example: the spaces for sentence with labels, button text, etc */ - --gap-block: 0.25rem; /* gap for element blocks, for example: spaces between buttons, menu image & title, header icon & title etc */ + --gap-block: 0.5rem; /* gap for element blocks, for example: spaces between buttons, menu image & title, header icon & title etc */ } @media (min-width: 768px) and (max-width: 1200px) { @@ -185,10 +185,6 @@ details summary { cursor: pointer; } -details summary > * { - display: inline; -} - progress { background: var(--color-secondary-dark-1); border-radius: var(--border-radius); @@ -474,15 +470,6 @@ a.label, color: var(--color-text-light-2); } -.ui.comments .comment .actions a { - color: var(--color-text-light); -} - -.ui.comments .comment .actions a.active, -.ui.comments .comment .actions a:hover { - color: var(--color-primary); -} - img.ui.avatar, .ui.avatar img, .ui.avatar svg { @@ -1080,7 +1067,9 @@ table th[data-sortt-desc] .svg { .btn, .ui.ui.button, .ui.ui.dropdown, -.flex-text-inline { +.flex-text-inline, +.flex-text-inline > a, +.flex-text-inline > span { display: inline-flex; align-items: center; gap: var(--gap-inline); diff --git a/web_src/css/modules/comment.css b/web_src/css/modules/comment.css index 9947b15b9ad..1e235a248fc 100644 --- a/web_src/css/modules/comment.css +++ b/web_src/css/modules/comment.css @@ -56,10 +56,6 @@ min-width: 0; } -.ui.comments .comment > .avatar ~ .content { - margin-left: 12px; -} - .ui.comments .comment .author { font-size: 1em; font-weight: var(--font-weight-medium); diff --git a/web_src/css/modules/label.css b/web_src/css/modules/label.css index f5d0decdf67..cf850e4c5aa 100644 --- a/web_src/css/modules/label.css +++ b/web_src/css/modules/label.css @@ -93,7 +93,6 @@ a.ui.label:hover { background: var(--color-button); border: 1px solid var(--color-light-border); color: var(--color-text-light); - padding: calc(0.5833em - 1px) calc(0.833em - 1px); } a.ui.basic.label:hover { text-decoration: none; @@ -254,6 +253,7 @@ a.ui.ui.ui.basic.grey.label:hover { color: var(--color-label-hover-bg); } +/* "horizontal label" is actually "fat label" which has enough padding spaces to be used standalone in headers */ .ui.horizontal.label { margin: 0 0.5em 0 0; padding: 0.4em 0.833em; diff --git a/web_src/css/modules/table.css b/web_src/css/modules/table.css index eabca31a170..6298471d474 100644 --- a/web_src/css/modules/table.css +++ b/web_src/css/modules/table.css @@ -167,6 +167,11 @@ text-overflow: ellipsis; } +.ui.selectable.table > tbody > tr:hover, +.ui.table tbody tr td.selectable:hover { + background: var(--color-hover); +} + .ui.attached.table { top: 0; bottom: 0; @@ -289,6 +294,9 @@ .ui.basic.striped.table > tbody > tr:nth-child(2n) { background: var(--color-light); } +.ui.basic.striped.selectable.table > tbody > tr:nth-child(2n):hover { + background: var(--color-hover); +} .ui[class*="very basic"].table { border: none; diff --git a/web_src/css/repo.css b/web_src/css/repo.css index a72709c3820..5238e3a2e52 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -141,7 +141,7 @@ td .commit-summary { align-items: center; overflow: hidden; text-overflow: ellipsis; - gap: 0.25em; + gap: 0.5em; } @media (max-width: 767.98px) { @@ -238,8 +238,8 @@ td .commit-summary { border-radius: var(--border-radius); } -.repository.file.editor .commit-form-wrapper .commit-form::before, -.repository.file.editor .commit-form-wrapper .commit-form::after { +.avatar-content-left-arrow::before, +.avatar-content-left-arrow::after { right: 100%; top: 20px; border: solid transparent; @@ -250,18 +250,24 @@ td .commit-summary { pointer-events: none; } -.repository.file.editor .commit-form-wrapper .commit-form::before { +.avatar-content-left-arrow::before { border-right-color: var(--color-secondary); border-width: 9px; margin-top: -9px; } -.repository.file.editor .commit-form-wrapper .commit-form::after { +.avatar-content-left-arrow::after { border-right-color: var(--color-box-body); border-width: 8px; margin-top: -8px; } +@media (max-width: 767.98px) { + .avatar-content-left-arrow::before, + .avatar-content-left-arrow::after { + display: none; + } +} .repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .branch-name { display: inline-block; padding: 2px 4px; @@ -294,30 +300,6 @@ td .commit-summary { min-width: 100px; } -.repository.new.issue .comment.form .content::before, -.repository.new.issue .comment.form .content::after { - right: 100%; - top: 20px; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; -} - -.repository.new.issue .comment.form .content::before { - border-right-color: var(--color-secondary); - border-width: 9px; - margin-top: -9px; -} - -.repository.new.issue .comment.form .content::after { - border-right-color: var(--color-box-body); - border-width: 8px; - margin-top: -8px; -} - .repository.new.issue .comment.form .content .markup { font-size: 14px; } @@ -326,21 +308,6 @@ td .commit-summary { display: inline-block; } -@media (max-width: 767.98px) { - .comment.form .issue-content-left .avatar { - display: none; - } - .comment.form .issue-content-left .content { - margin-left: 0 !important; - } - .comment.form .issue-content-left .content::before, - .comment.form .issue-content-left .content::after, - .comment.form .content .form::before, - .comment.form .content .form::after { - display: none; - } -} - /* issue title & meta & edit */ .issue-title-header { width: 100%; @@ -365,10 +332,9 @@ td .commit-summary { .repository.view.issue .issue-title { display: flex; - align-items: center; gap: 0.5em; margin-bottom: 8px; - min-height: 40px; /* avoid layout shift on edit */ + min-height: 36px; /* avoid layout shift on edit */ } .repository.view.issue .issue-title h1 { @@ -376,9 +342,10 @@ td .commit-summary { width: 100%; font-weight: var(--font-weight-normal); font-size: 32px; - line-height: 40px; + line-height: 36px; /* vertically center single-line text with .issue-title-buttons */ margin: 0; padding-right: 0.25rem; + overflow-wrap: anywhere; } @media (max-width: 767.98px) { @@ -535,6 +502,7 @@ td .commit-summary { background-color: var(--color-timeline); border-radius: var(--border-radius-full); display: flex; + flex-shrink: 0; float: left; margin-left: -33px; margin-right: 8px; @@ -562,9 +530,18 @@ td .commit-summary { margin-left: -16px; } -.repository.view.issue .comment-list .timeline-item.event > .text { +.repository.view.issue .comment-list .timeline-item .comment-text-line { line-height: 32px; vertical-align: middle; + color: var(--color-text-light); +} + +.repository.view.issue .comment-list .timeline-item .comment-text-line a { + color: inherit; +} + +.repository.view.issue .comment-list .timeline-item .avatar-with-link + .comment-text-line { + margin-left: 0.25em; } .repository.view.issue .comment-list .timeline-item.commits-list { @@ -578,15 +555,11 @@ td .commit-summary { margin-top: 4px; } -.repository.view.issue .comment-list .timeline-item .comparebox { - line-height: 32px; +.repository.view.issue .comment-list .timeline-item .comment-text-label { vertical-align: middle; -} - -.repository.view.issue .comment-list .timeline-item .comparebox .compare.label { - font-size: 1rem; - margin: 0; border: 1px solid var(--color-light-border); + height: 26px; + margin: 4px 0; /* because this label is beside the comment line, which has "line-height: 34px" */ } @media (max-width: 767.98px) { @@ -650,30 +623,6 @@ td .commit-summary { width: calc(100% + 2rem); } -.repository.view.issue .comment-list .comment .merge-section.no-header::before, -.repository.view.issue .comment-list .comment .merge-section.no-header::after { - right: 100%; - top: 20px; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; -} - -.repository.view.issue .comment-list .comment .merge-section.no-header::before { - border-right-color: var(--color-secondary); - border-width: 9px; - margin-top: -9px; -} - -.repository.view.issue .comment-list .comment .merge-section.no-header::after { - border-right-color: var(--color-box-body); - border-width: 8px; - margin-top: -8px; -} - .merge-section-info code { border: 1px solid var(--color-light-border); border-radius: var(--border-radius); @@ -717,19 +666,10 @@ td .commit-summary { padding: 0 !important; } -.repository.view.issue .comment-list .code-comment .comment-header::after, -.repository.view.issue .comment-list .code-comment .comment-header::before { - display: none; -} - .repository.view.issue .comment-list .code-comment .comment-content { margin-left: 24px; } -.repository.view.issue .comment-list .comment > .avatar { - margin-top: 6px; -} - .repository.view.issue .comment-list .comment-code-cloud button.comment-form-reply { margin: 0; } @@ -761,30 +701,6 @@ td .commit-summary { clear: none; } -.repository .comment.form .content .segment::before, -.repository .comment.form .content .segment::after { - right: 100%; - top: 20px; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; -} - -.repository .comment.form .content .segment::before { - border-right-color: var(--color-secondary); - border-width: 9px; - margin-top: -9px; -} - -.repository .comment.form .content .segment::after { - border-right-color: var(--color-box-body); - border-width: 8px; - margin-top: -8px; -} - .repository.new.milestone textarea { height: 200px; } @@ -807,30 +723,6 @@ td .commit-summary { text-align: center; } -.repository.compare.pull .comment.form .content::before, -.repository.compare.pull .comment.form .content::after { - right: 100%; - top: 20px; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; -} - -.repository.compare.pull .comment.form .content::before { - border-right-color: var(--color-secondary); - border-width: 9px; - margin-top: -9px; -} - -.repository.compare.pull .comment.form .content::after { - border-right-color: var(--color-box-body); - border-width: 8px; - margin-top: -8px; -} - .repository.compare.pull .markup { font-size: 14px; } @@ -1420,37 +1312,15 @@ td .commit-summary { .comment-header { background: var(--color-box-header); border-bottom: 1px solid var(--color-secondary); - padding: 0 1rem; + padding: 0.5em 1rem; position: relative; color: var(--color-text); min-height: 41px; display: flex; justify-content: space-between; align-items: center; -} - -.comment-header::before, -.comment-header::after { - right: 100%; - top: 20px; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; -} - -.comment-header::before { - border-right-color: var(--color-secondary); - border-width: 9px; - margin-top: -9px; -} - -.comment-header::after { - border-right-color: var(--color-box-header); - border-width: 8px; - margin-top: -8px; + flex-wrap: wrap; + gap: 0.25em; } .comment-header.arrow-top::before, @@ -1468,17 +1338,16 @@ td .commit-summary { left: 7px; } -.comment-header .actions a:not(.label) { - padding: 0.5rem !important; -} - -.comment-header .actions .label { - margin: 0 !important; -} - .comment-header-left, .comment-header-right { - gap: 4px; + display: flex; + align-items: center; + gap: 0.5em; +} + +.comment-header-right { + flex: 1; + justify-content: end; } .comment-body { @@ -1628,7 +1497,7 @@ tbody.commit-list { overflow-wrap: anywhere; } -.content-history-detail-dialog .header .avatar { +.content-history-detail-dialog .header .ui.avatar { position: relative; top: -2px; } @@ -1977,10 +1846,6 @@ tbody.commit-list { .repository.view.issue .comment-list .timeline .comment-header { padding-left: 4px; } - .repository.view.issue .comment-list .timeline .comment-header::before, - .repository.view.issue .comment-list .timeline .comment-header::after { - content: unset; - } /* Don't show the general avatar, we show the inline avatar on mobile. * And don't show the role labels, there's no place for that. */ .repository.view.issue .comment-list .timeline .timeline-avatar, @@ -2014,15 +1879,6 @@ tbody.commit-list { .commit-table th.sha { display: none !important; } - .comment-header { - flex-wrap: wrap; - } - .comment-header .comment-header-left { - flex-wrap: wrap; - } - .comment-header .comment-header-right { - margin-left: auto; - } } .commit-status-header { diff --git a/web_src/css/repo/home-file-list.css b/web_src/css/repo/home-file-list.css index f2ab052a54c..6aa9e4bca3d 100644 --- a/web_src/css/repo/home-file-list.css +++ b/web_src/css/repo/home-file-list.css @@ -71,7 +71,7 @@ #repo-files-table .repo-file-cell.name .entry-name { flex-shrink: 1; - min-width: 3em; + min-width: 1ch; /* leave about one letter space when shrinking, need to fine tune the "shrinks" in this grid in the future */ } @media (max-width: 767.98px) { diff --git a/web_src/css/user.css b/web_src/css/user.css index caabf1834cb..d42e8688fbc 100644 --- a/web_src/css/user.css +++ b/web_src/css/user.css @@ -114,6 +114,14 @@ border-radius: var(--border-radius); } +.notifications-item { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.5em; + padding: 0.5em 1em; +} + .notifications-item:hover { background: var(--color-hover); } @@ -129,6 +137,9 @@ .notifications-item:hover .notifications-buttons { display: flex; + align-items: center; + justify-content: end; + gap: 0.25em; } .notifications-item:hover .notifications-updated { diff --git a/web_src/js/features/admin/common.ts b/web_src/js/features/admin/common.ts index 4ed5d62eeee..dd5b1f464d4 100644 --- a/web_src/js/features/admin/common.ts +++ b/web_src/js/features/admin/common.ts @@ -102,6 +102,9 @@ function initAdminAuthentication() { break; } } + + const supportSshPublicKey = document.querySelector(`#${provider}_SupportSSHPublicKey`)?.value === 'true'; + toggleElem('.field.oauth2_ssh_public_key_claim_name', supportSshPublicKey); onOAuth2UseCustomURLChange(applyDefaultValues); } diff --git a/web_src/js/features/common-button.test.ts b/web_src/js/features/common-button.test.ts index f41bafbc791..4ae1f748977 100644 --- a/web_src/js/features/common-button.test.ts +++ b/web_src/js/features/common-button.test.ts @@ -1,12 +1,23 @@ -import {assignElementProperty} from './common-button.ts'; +import {assignElementProperty, type ElementWithAssignableProperties} from './common-button.ts'; test('assignElementProperty', () => { const elForm = document.createElement('form'); assignElementProperty(elForm, 'action', '/test-link'); expect(elForm.action).contains('/test-link'); // the DOM always returns absolute URL + expect(elForm.getAttribute('action')).eq('/test-link'); assignElementProperty(elForm, 'text-content', 'dummy'); expect(elForm.textContent).toBe('dummy'); + // mock a form with its property "action" overwritten by an input element + const elFormWithAction = new class implements ElementWithAssignableProperties { + action = document.createElement('input'); // now "form.action" is not string, but an input element + _attrs: Record = {}; + setAttribute(name: string, value: string) { this._attrs[name] = value } + getAttribute(name: string): string | null { return this._attrs[name] } + }(); + assignElementProperty(elFormWithAction, 'action', '/bar'); + expect(elFormWithAction.getAttribute('action')).eq('/bar'); + const elInput = document.createElement('input'); expect(elInput.readOnly).toBe(false); assignElementProperty(elInput, 'read-only', 'true'); diff --git a/web_src/js/features/common-button.ts b/web_src/js/features/common-button.ts index 3b112b116b8..0326956222c 100644 --- a/web_src/js/features/common-button.ts +++ b/web_src/js/features/common-button.ts @@ -109,18 +109,26 @@ function onHidePanelClick(el: HTMLElement, e: MouseEvent) { throw new Error('no panel to hide'); // should never happen, otherwise there is a bug in code } -export function assignElementProperty(el: any, name: string, val: string) { - name = camelize(name); - const old = el[name]; +export type ElementWithAssignableProperties = { + getAttribute: (name: string) => string | null; + setAttribute: (name: string, value: string) => void; +} & Record + +export function assignElementProperty(el: ElementWithAssignableProperties, kebabName: string, val: string) { + const camelizedName = camelize(kebabName); + const old = el[camelizedName]; if (typeof old === 'boolean') { - el[name] = val === 'true'; + el[camelizedName] = val === 'true'; } else if (typeof old === 'number') { - el[name] = parseFloat(val); + el[camelizedName] = parseFloat(val); } else if (typeof old === 'string') { - el[name] = val; + el[camelizedName] = val; + } else if (old?.nodeName) { + // "form" has an edge case: its "" element overwrites the "action" property, we can only set attribute + el.setAttribute(kebabName, val); } else { // in the future, we could introduce a better typing system like `data-modal-form.action:string="..."` - throw new Error(`cannot assign element property ${name} by value ${val}`); + throw new Error(`cannot assign element property "${camelizedName}" by value "${val}"`); } } diff --git a/web_src/js/features/file-view.ts b/web_src/js/features/file-view.ts index d803f53c0de..67f43814684 100644 --- a/web_src/js/features/file-view.ts +++ b/web_src/js/features/file-view.ts @@ -2,7 +2,7 @@ import type {FileRenderPlugin} from '../render/plugin.ts'; import {newRenderPlugin3DViewer} from '../render/plugins/3d-viewer.ts'; import {newRenderPluginPdfViewer} from '../render/plugins/pdf-viewer.ts'; import {registerGlobalInitFunc} from '../modules/observer.ts'; -import {createElementFromHTML, showElem, toggleClass} from '../utils/dom.ts'; +import {createElementFromHTML, showElem, toggleElemClass} from '../utils/dom.ts'; import {html} from '../utils/html.ts'; import {basename} from '../utils.ts'; @@ -21,8 +21,8 @@ function showRenderRawFileButton(elFileView: HTMLElement, renderContainer: HTMLE const toggleButtons = elFileView.querySelector('.file-view-toggle-buttons'); showElem(toggleButtons); const displayingRendered = Boolean(renderContainer); - toggleClass(toggleButtons.querySelectorAll('.file-view-toggle-source'), 'active', !displayingRendered); // it may not exist - toggleClass(toggleButtons.querySelector('.file-view-toggle-rendered'), 'active', displayingRendered); + toggleElemClass(toggleButtons.querySelectorAll('.file-view-toggle-source'), 'active', !displayingRendered); // it may not exist + toggleElemClass(toggleButtons.querySelector('.file-view-toggle-rendered'), 'active', displayingRendered); // TODO: if there is only one button, hide it? } diff --git a/web_src/js/features/notification.ts b/web_src/js/features/notification.ts index dc0acb0244e..4a1aa3ede94 100644 --- a/web_src/js/features/notification.ts +++ b/web_src/js/features/notification.ts @@ -1,40 +1,13 @@ import {GET} from '../modules/fetch.ts'; -import {toggleElem, type DOMEvent, createElementFromHTML} from '../utils/dom.ts'; +import {toggleElem, createElementFromHTML} from '../utils/dom.ts'; import {logoutFromWorker} from '../modules/worker.ts'; const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config; let notificationSequenceNumber = 0; -export function initNotificationsTable() { - const table = document.querySelector('#notification_table'); - if (!table) return; - - // when page restores from bfcache, delete previously clicked items - window.addEventListener('pageshow', (e) => { - if (e.persisted) { // page was restored from bfcache - const table = document.querySelector('#notification_table'); - const unreadCountEl = document.querySelector('.notifications-unread-count'); - let unreadCount = parseInt(unreadCountEl.textContent); - for (const item of table.querySelectorAll('.notifications-item[data-remove="true"]')) { - item.remove(); - unreadCount -= 1; - } - unreadCountEl.textContent = String(unreadCount); - } - }); - - // mark clicked unread links for deletion on bfcache restore - for (const link of table.querySelectorAll('.notifications-item[data-status="1"] .notifications-link')) { - link.addEventListener('click', (e: DOMEvent) => { - e.target.closest('.notifications-item').setAttribute('data-remove', 'true'); - }); - } -} - -async function receiveUpdateCount(event: MessageEvent) { +async function receiveUpdateCount(event: MessageEvent<{type: string, data: string}>) { try { - const data = JSON.parse(event.data); - + const data = JSON.parse(event.data.data); for (const count of document.querySelectorAll('.notification_count')) { count.classList.toggle('tw-hidden', data.Count === 0); count.textContent = `${data.Count}`; @@ -71,7 +44,7 @@ export function initNotificationCount() { type: 'start', url: `${window.location.origin}${appSubUrl}/user/events`, }); - worker.port.addEventListener('message', (event: MessageEvent) => { + worker.port.addEventListener('message', (event: MessageEvent<{type: string, data: string}>) => { if (!event.data || !event.data.type) { console.error('unknown worker message event', event); return; @@ -144,11 +117,11 @@ async function updateNotificationCountWithCallback(callback: (timeout: number, n } async function updateNotificationTable() { - const notificationDiv = document.querySelector('#notification_div'); + let notificationDiv = document.querySelector('#notification_div'); if (notificationDiv) { try { const params = new URLSearchParams(window.location.search); - params.set('div-only', String(true)); + params.set('div-only', 'true'); params.set('sequence-number', String(++notificationSequenceNumber)); const response = await GET(`${appSubUrl}/notifications?${params.toString()}`); @@ -160,7 +133,8 @@ async function updateNotificationTable() { const el = createElementFromHTML(data); if (parseInt(el.getAttribute('data-sequence-number')) === notificationSequenceNumber) { notificationDiv.outerHTML = data; - initNotificationsTable(); + notificationDiv = document.querySelector('#notification_div'); + window.htmx.process(notificationDiv); // when using htmx, we must always remember to process the new content changed by us } } catch (error) { console.error(error); diff --git a/web_src/js/features/repo-graph.ts b/web_src/js/features/repo-graph.ts index 036a55f7152..ebca6e212a7 100644 --- a/web_src/js/features/repo-graph.ts +++ b/web_src/js/features/repo-graph.ts @@ -1,4 +1,4 @@ -import {toggleClass} from '../utils/dom.ts'; +import {toggleElemClass} from '../utils/dom.ts'; import {GET} from '../modules/fetch.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; @@ -9,11 +9,11 @@ export function initRepoGraphGit() { const elColorMonochrome = document.querySelector('#flow-color-monochrome'); const elColorColored = document.querySelector('#flow-color-colored'); const toggleColorMode = (mode: 'monochrome' | 'colored') => { - toggleClass(graphContainer, 'monochrome', mode === 'monochrome'); - toggleClass(graphContainer, 'colored', mode === 'colored'); + toggleElemClass(graphContainer, 'monochrome', mode === 'monochrome'); + toggleElemClass(graphContainer, 'colored', mode === 'colored'); - toggleClass(elColorMonochrome, 'active', mode === 'monochrome'); - toggleClass(elColorColored, 'active', mode === 'colored'); + toggleElemClass(elColorMonochrome, 'active', mode === 'monochrome'); + toggleElemClass(elColorColored, 'active', mode === 'colored'); const params = new URLSearchParams(window.location.search); params.set('mode', mode); diff --git a/web_src/js/features/repo-settings.ts b/web_src/js/features/repo-settings.ts index be1821664f6..5c81cf5ecd9 100644 --- a/web_src/js/features/repo-settings.ts +++ b/web_src/js/features/repo-settings.ts @@ -1,6 +1,6 @@ import {minimatch} from 'minimatch'; import {createMonaco} from './codeeditor.ts'; -import {onInputDebounce, queryElems, toggleClass, toggleElem} from '../utils/dom.ts'; +import {onInputDebounce, queryElems, toggleElem} from '../utils/dom.ts'; import {POST} from '../modules/fetch.ts'; import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; @@ -124,14 +124,18 @@ function initRepoSettingsOptions() { const pageContent = document.querySelector('.page-content.repository.settings.options'); if (!pageContent) return; - // Enable or select internal/external wiki system and issue tracker. + // toggle related panels for the checkbox/radio inputs, the "selector" may not exist + const toggleTargetContextPanel = (selector: string, enabled: boolean) => { + if (!selector) return; + queryElems(document, selector, (el) => el.classList.toggle('disabled', !enabled)); + }; queryElems(pageContent, '.enable-system', (el) => el.addEventListener('change', () => { - toggleClass(el.getAttribute('data-target'), 'disabled', !el.checked); - toggleClass(el.getAttribute('data-context'), 'disabled', el.checked); + toggleTargetContextPanel(el.getAttribute('data-target'), el.checked); + toggleTargetContextPanel(el.getAttribute('data-context'), !el.checked); })); queryElems(pageContent, '.enable-system-radio', (el) => el.addEventListener('change', () => { - toggleClass(el.getAttribute('data-target'), 'disabled', el.value === 'false'); - toggleClass(el.getAttribute('data-context'), 'disabled', el.value === 'true'); + toggleTargetContextPanel(el.getAttribute('data-target'), el.value === 'true'); + toggleTargetContextPanel(el.getAttribute('data-context'), el.value === 'false'); })); queryElems(pageContent, '.js-tracker-issue-style', (el) => el.addEventListener('change', () => { diff --git a/web_src/js/index-domready.ts b/web_src/js/index-domready.ts index ca18d1e8281..770c7fc00c6 100644 --- a/web_src/js/index-domready.ts +++ b/web_src/js/index-domready.ts @@ -15,7 +15,7 @@ import {initTableSort} from './features/tablesort.ts'; import {initAdminUserListSearchForm} from './features/admin/users.ts'; import {initAdminConfigs} from './features/admin/config.ts'; import {initMarkupAnchors} from './markup/anchors.ts'; -import {initNotificationCount, initNotificationsTable} from './features/notification.ts'; +import {initNotificationCount} from './features/notification.ts'; import {initRepoIssueContentHistory} from './features/repo-issue-content.ts'; import {initStopwatch} from './features/stopwatch.ts'; import {initFindFileInRepo} from './features/repo-findfile.ts'; @@ -117,7 +117,6 @@ const initPerformanceTracer = callInitFunctions([ initDashboardRepoList, initNotificationCount, - initNotificationsTable, initOrgTeam, diff --git a/web_src/js/index.ts b/web_src/js/index.ts index e78b3cb64f3..af53cc488cd 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -9,5 +9,15 @@ import {onDomReady} from './utils/dom.ts'; import 'htmx.org'; onDomReady(async () => { - await import(/* webpackChunkName: "index-domready" */'./index-domready.ts'); + // when navigate before the import complete, there will be an error from webpack chunk loader: + // JavaScript promise rejection: Loading chunk index-domready failed. + try { + await import(/* webpackChunkName: "index-domready" */'./index-domready.ts'); + } catch (e) { + if (e.name === 'ChunkLoadError') { + console.error('Error loading index-domready:', e); + } else { + throw e; + } + } }); diff --git a/web_src/js/modules/toast.ts b/web_src/js/modules/toast.ts index ed807a49772..087103cbd8e 100644 --- a/web_src/js/modules/toast.ts +++ b/web_src/js/modules/toast.ts @@ -44,7 +44,7 @@ type ToastifyElement = HTMLElement & {_giteaToastifyInstance?: Toast }; // See https://github.com/apvarun/toastify-js#api for options function showToast(message: string, level: Intent, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}): Toast { - const body = useHtmlBody ? String(message) : htmlEscape(message); + const body = useHtmlBody ? message : htmlEscape(message); const parent = document.querySelector('.ui.dimmer.active') ?? document.body; const duplicateKey = preventDuplicates ? (preventDuplicates === true ? `${level}-${body}` : preventDuplicates) : ''; diff --git a/web_src/js/utils.ts b/web_src/js/utils.ts index e33b1413e8a..f396a8e4f6f 100644 --- a/web_src/js/utils.ts +++ b/web_src/js/utils.ts @@ -1,6 +1,6 @@ import {decode, encode} from 'uint8-to-base64'; import type {IssuePageInfo, IssuePathInfo, RepoOwnerPathInfo} from './types.ts'; -import {toggleClass, toggleElem} from './utils/dom.ts'; +import {toggleElemClass, toggleElem} from './utils/dom.ts'; // transform /path/to/file.ext to /path/to export function dirname(path: string): string { @@ -194,7 +194,7 @@ export function toggleFullScreen(fullscreenElementsSelector: string, isFullScree const fullScreenEl = document.querySelector(fullscreenElementsSelector); const outerEl = document.querySelector('.full.height'); - toggleClass(fullscreenElementsSelector, 'fullscreen', isFullScreen); + toggleElemClass(fullscreenElementsSelector, 'fullscreen', isFullScreen); if (isFullScreen) { outerEl.append(fullScreenEl); } else { diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts index 3b14b9bceac..6d6a3735dab 100644 --- a/web_src/js/utils/dom.ts +++ b/web_src/js/utils/dom.ts @@ -25,7 +25,7 @@ function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: a throw new Error('invalid argument to be shown/hidden'); } -export function toggleClass(el: ElementArg, className: string, force?: boolean): ArrayLikeIterable { +export function toggleElemClass(el: ElementArg, className: string, force?: boolean): ArrayLikeIterable { return elementsCall(el, (e: Element) => { if (force === true) { e.classList.add(className); @@ -44,7 +44,7 @@ export function toggleClass(el: ElementArg, className: string, force?: boolean): * @param force force=true to show or force=false to hide, undefined to toggle */ export function toggleElem(el: ElementArg, force?: boolean): ArrayLikeIterable { - return toggleClass(el, 'tw-hidden', force === undefined ? force : !force); + return toggleElemClass(el, 'tw-hidden', force === undefined ? force : !force); } export function showElem(el: ElementArg): ArrayLikeIterable { @@ -283,7 +283,7 @@ export function isElemVisible(el: HTMLElement): boolean { // This function DOESN'T account for all possible visibility scenarios, its behavior is covered by the tests of "querySingleVisibleElem" if (!el) return false; // checking el.style.display is not necessary for browsers, but it is required by some tests with happy-dom because happy-dom doesn't really do layout - return !el.classList.contains('tw-hidden') && Boolean((el.offsetWidth || el.offsetHeight || el.getClientRects().length) && el.style.display !== 'none'); + return !el.classList.contains('tw-hidden') && (el.offsetWidth || el.offsetHeight || el.getClientRects().length) && el.style.display !== 'none'; } // replace selected text in a textarea while preserving editor history, e.g. CTRL-Z works after this