summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.deadcode-out1
-rw-r--r--.dockerignore2
-rw-r--r--.eslintrc.yaml10
-rw-r--r--.forgejo/workflows/backport.yml22
-rw-r--r--.forgejo/workflows/renovate.yml10
-rw-r--r--.forgejo/workflows/testing.yml5
-rw-r--r--Makefile25
-rw-r--r--assets/go-licenses.json5
-rwxr-xr-xbuild/crowdin-to-weblate.sh27
-rw-r--r--build/merge-forgejo-locales.go100
-rw-r--r--cmd/dump.go18
-rw-r--r--cmd/dump_test.go117
-rw-r--r--cmd/hook.go81
-rw-r--r--cmd/hook_test.go91
-rw-r--r--cmd/serv.go6
-rw-r--r--custom/conf/app.example.ini4
-rw-r--r--docs/content/administration/config-cheat-sheet.en-us.md2
-rw-r--r--docs/content/administration/config-cheat-sheet.zh-cn.md2
-rw-r--r--docs/content/contributing/guidelines-frontend.en-us.md4
-rw-r--r--docs/content/contributing/guidelines-frontend.zh-cn.md4
-rw-r--r--go.mod11
-rw-r--r--go.sum38
-rw-r--r--models/asymkey/gpg_key_commit_verification.go492
-rw-r--r--models/asymkey/gpg_key_object_verification.go527
-rw-r--r--models/asymkey/gpg_key_tag_verification.go15
-rw-r--r--models/asymkey/ssh_key_object_verification.go (renamed from models/asymkey/ssh_key_commit_verification.go)15
-rw-r--r--models/asymkey/ssh_key_object_verification_test.go (renamed from models/asymkey/ssh_key_commit_verification_test.go)18
-rw-r--r--models/db/context.go10
-rw-r--r--models/fixtures/comment.yml8
-rw-r--r--models/fixtures/project.yml24
-rw-r--r--models/fixtures/project_board.yml46
-rw-r--r--models/fixtures/review.yml9
-rw-r--r--models/git/commit_status.go120
-rw-r--r--models/issues/comment_code.go4
-rw-r--r--models/issues/issue_project.go19
-rw-r--r--models/issues/issue_search.go22
-rw-r--r--models/issues/issue_stats.go6
-rw-r--r--models/issues/pull.go87
-rw-r--r--models/issues/review.go38
-rw-r--r--models/issues/review_test.go30
-rw-r--r--models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml23
-rw-r--r--models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml26
-rw-r--r--models/migrations/migrations.go6
-rw-r--r--models/migrations/v1_22/v291.go14
-rw-r--r--models/migrations/v1_22/v292.go9
-rw-r--r--models/migrations/v1_22/v293.go108
-rw-r--r--models/migrations/v1_22/v293_test.go44
-rw-r--r--models/project/board.go84
-rw-r--r--models/project/board_test.go44
-rw-r--r--models/project/project_test.go12
-rw-r--r--models/user/email_address.go8
-rw-r--r--modules/git/repo_attribute.go275
-rw-r--r--modules/git/repo_attribute_test.go326
-rw-r--r--modules/gitgraph/graph_models.go2
-rw-r--r--modules/graceful/manager.go6
-rw-r--r--modules/graceful/manager_common.go8
-rw-r--r--modules/graceful/manager_unix.go2
-rw-r--r--modules/graceful/manager_windows.go2
-rw-r--r--modules/highlight/highlight.go4
-rw-r--r--modules/highlight/highlight_test.go2
-rw-r--r--modules/indexer/internal/paginator.go21
-rw-r--r--modules/indexer/issues/db/db.go11
-rw-r--r--modules/indexer/issues/indexer.go2
-rw-r--r--modules/indexer/issues/internal/model.go2
-rw-r--r--modules/indexer/issues/internal/tests/tests.go7
-rw-r--r--modules/indexer/issues/meilisearch/meilisearch.go12
-rw-r--r--modules/markup/file_preview.go323
-rw-r--r--modules/markup/html.go42
-rw-r--r--modules/markup/html_test.go67
-rw-r--r--modules/markup/markdown/color_util.go19
-rw-r--r--modules/markup/markdown/color_util_test.go50
-rw-r--r--modules/markup/markdown/goldmark.go3
-rw-r--r--modules/markup/renderer.go1
-rw-r--r--modules/markup/sanitizer.go17
-rw-r--r--modules/markup/tests/repo/repo1_filepreview/HEAD1
-rw-r--r--modules/markup/tests/repo/repo1_filepreview/config6
-rw-r--r--modules/markup/tests/repo/repo1_filepreview/description1
-rw-r--r--modules/markup/tests/repo/repo1_filepreview/info/exclude6
-rw-r--r--modules/markup/tests/repo/repo1_filepreview/objects/19/0d9492934af498c3f669d6a2431dc5459e5b20bin0 -> 120 bytes
-rw-r--r--modules/markup/tests/repo/repo1_filepreview/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904bin0 -> 15 bytes
-rw-r--r--modules/markup/tests/repo/repo1_filepreview/objects/83/57a737d04385bb7f2ab59ff184be94756e7972bin0 -> 44 bytes
-rw-r--r--modules/markup/tests/repo/repo1_filepreview/objects/84/22d40f12717e1ebd5cef2449f6c09d1f775969bin0 -> 23 bytes
-rw-r--r--modules/markup/tests/repo/repo1_filepreview/objects/d4/490327def9658be036d6a52c4417d84e74dd4cbin0 -> 46 bytes
-rw-r--r--modules/markup/tests/repo/repo1_filepreview/objects/ee/2b1253d9cf407796e2e724926cbe3a974b214d1
-rw-r--r--modules/markup/tests/repo/repo1_filepreview/refs/heads/master1
-rw-r--r--modules/process/manager.go4
-rw-r--r--modules/setting/markup.go2
-rw-r--r--modules/setting/security.go2
-rw-r--r--modules/templates/util_render.go4
-rw-r--r--modules/testlogger/testlogger.go303
-rw-r--r--options/locale/locale_bg.ini95
-rw-r--r--options/locale/locale_cs-CZ.ini21
-rw-r--r--options/locale/locale_de-DE.ini29
-rw-r--r--options/locale/locale_el-GR.ini6
-rw-r--r--options/locale/locale_en-US.ini86
-rw-r--r--options/locale/locale_es-ES.ini6
-rw-r--r--options/locale/locale_fi-FI.ini4
-rw-r--r--options/locale/locale_fil.ini119
-rw-r--r--options/locale/locale_fr-FR.ini6
-rw-r--r--options/locale/locale_is-IS.ini2
-rw-r--r--options/locale/locale_it-IT.ini6
-rw-r--r--options/locale/locale_ja-JP.ini15
-rw-r--r--options/locale/locale_lv-LV.ini6
-rw-r--r--options/locale/locale_nl-NL.ini20
-rw-r--r--options/locale/locale_pt-BR.ini6
-rw-r--r--options/locale/locale_pt-PT.ini6
-rw-r--r--options/locale/locale_ru-RU.ini20
-rw-r--r--options/locale/locale_tr-TR.ini6
-rw-r--r--options/locale/locale_zh-CN.ini36
-rw-r--r--options/locale/locale_zh-TW.ini6
-rw-r--r--package-lock.json580
-rw-r--r--package.json27
-rw-r--r--poetry.lock9
-rw-r--r--routers/api/packages/generic/generic.go29
-rw-r--r--routers/api/packages/generic/generic_test.go65
-rw-r--r--routers/api/v1/repo/file.go2
-rw-r--r--routers/api/v1/repo/pull_review.go17
-rw-r--r--routers/web/org/projects.go108
-rw-r--r--routers/web/org/setting.go2
-rw-r--r--routers/web/repo/blame.go4
-rw-r--r--routers/web/repo/issue.go6
-rw-r--r--routers/web/repo/issue_content_history.go2
-rw-r--r--routers/web/repo/projects.go52
-rw-r--r--routers/web/repo/pull_review.go4
-rw-r--r--routers/web/repo/pull_review_test.go27
-rw-r--r--routers/web/repo/release.go43
-rw-r--r--routers/web/repo/render.go15
-rw-r--r--routers/web/repo/setting/webhook.go10
-rw-r--r--routers/web/repo/view.go4
-rw-r--r--routers/web/user/setting/webhooks.go2
-rw-r--r--routers/web/web.go2
-rw-r--r--services/auth/source/oauth2/providers.go2
-rw-r--r--services/auth/source/oauth2/providers_base.go4
-rw-r--r--services/auth/source/oauth2/providers_openid.go2
-rw-r--r--services/doctor/fix16961.go6
-rw-r--r--services/doctor/storage.go2
-rw-r--r--services/issue/assignee.go23
-rw-r--r--services/issue/issue.go17
-rw-r--r--services/issue/pull.go147
-rw-r--r--services/markup/processorhelper.go54
-rw-r--r--services/migrations/migrate.go3
-rw-r--r--services/pull/pull.go7
-rw-r--r--services/pull/review.go33
-rw-r--r--services/pull/review_test.go48
-rw-r--r--services/user/user.go2
-rw-r--r--services/user/user_test.go32
-rw-r--r--services/webhook/default.go10
-rw-r--r--services/webhook/deliver_test.go1
-rw-r--r--services/webhook/dingtalk.go25
-rw-r--r--services/webhook/discord.go2
-rw-r--r--services/webhook/feishu.go2
-rw-r--r--services/webhook/general.go8
-rw-r--r--services/webhook/gogs.go2
-rw-r--r--services/webhook/matrix.go6
-rw-r--r--services/webhook/msteams.go2
-rw-r--r--services/webhook/packagist.go2
-rw-r--r--services/webhook/slack.go2
-rw-r--r--services/webhook/telegram.go2
-rw-r--r--services/webhook/webhook.go2
-rw-r--r--services/webhook/wechatwork.go5
-rw-r--r--tailwind.config.js6
-rw-r--r--templates/admin/auth/edit.tmpl8
-rw-r--r--templates/admin/auth/new.tmpl6
-rw-r--r--templates/admin/auth/source/ldap.tmpl14
-rw-r--r--templates/admin/auth/source/oauth.tmpl2
-rw-r--r--templates/admin/auth/source/smtp.tmpl2
-rw-r--r--templates/admin/auth/source/sspi.tmpl2
-rw-r--r--templates/admin/config.tmpl4
-rw-r--r--templates/admin/config_settings.tmpl2
-rw-r--r--templates/admin/cron.tmpl2
-rw-r--r--templates/admin/dashboard.tmpl2
-rw-r--r--templates/admin/emails/list.tmpl4
-rw-r--r--templates/admin/layout_head.tmpl2
-rw-r--r--templates/admin/org/list.tmpl4
-rw-r--r--templates/admin/queue_manage.tmpl2
-rw-r--r--templates/admin/repo/unadopted.tmpl4
-rw-r--r--templates/admin/self_check.tmpl6
-rw-r--r--templates/admin/stacktrace-row.tmpl10
-rw-r--r--templates/admin/user/edit.tmpl12
-rw-r--r--templates/admin/user/new.tmpl6
-rw-r--r--templates/base/head_navbar.tmpl32
-rw-r--r--templates/base/paginate.tmpl8
-rw-r--r--templates/devtest/fetch-action.tmpl2
-rw-r--r--templates/devtest/flex-list.tmpl8
-rw-r--r--templates/devtest/gitea-ui.tmpl19
-rw-r--r--templates/explore/navbar.tmpl2
-rw-r--r--templates/explore/repo_list.tmpl14
-rw-r--r--templates/explore/search.tmpl4
-rw-r--r--templates/home.tmpl2
-rw-r--r--templates/install.tmpl20
-rw-r--r--templates/org/follow_unfollow.tmpl2
-rw-r--r--templates/org/header.tmpl4
-rw-r--r--templates/org/home.tmpl4
-rw-r--r--templates/org/menu.tmpl2
-rw-r--r--templates/org/projects/list.tmpl2
-rw-r--r--templates/org/settings/options.tmpl2
-rw-r--r--templates/org/team/members.tmpl2
-rw-r--r--templates/org/team/new.tmpl8
-rw-r--r--templates/org/team/repositories.tmpl2
-rw-r--r--templates/package/metadata/alpine.tmpl2
-rw-r--r--templates/package/metadata/cargo.tmpl10
-rw-r--r--templates/package/metadata/chef.tmpl6
-rw-r--r--templates/package/metadata/composer.tmpl6
-rw-r--r--templates/package/metadata/conan.tmpl8
-rw-r--r--templates/package/metadata/conda.tmpl8
-rw-r--r--templates/package/metadata/container.tmpl14
-rw-r--r--templates/package/metadata/helm.tmpl4
-rw-r--r--templates/package/metadata/maven.tmpl6
-rw-r--r--templates/package/metadata/npm.tmpl8
-rw-r--r--templates/package/metadata/nuget.tmpl6
-rw-r--r--templates/package/metadata/pub.tmpl6
-rw-r--r--templates/package/metadata/pypi.tmpl6
-rw-r--r--templates/package/metadata/rpm.tmpl4
-rw-r--r--templates/package/metadata/rubygems.tmpl6
-rw-r--r--templates/package/metadata/swift.tmpl2
-rw-r--r--templates/package/metadata/vagrant.tmpl6
-rw-r--r--templates/package/shared/list.tmpl2
-rw-r--r--templates/package/shared/versionlist.tmpl2
-rw-r--r--templates/package/view.tmpl14
-rw-r--r--templates/projects/list.tmpl10
-rw-r--r--templates/projects/view.tmpl41
-rw-r--r--templates/repo/actions/list.tmpl2
-rw-r--r--templates/repo/actions/no_workflows.tmpl4
-rw-r--r--templates/repo/actions/runs_list.tmpl4
-rw-r--r--templates/repo/blame.tmpl20
-rw-r--r--templates/repo/branch/list.tmpl40
-rw-r--r--templates/repo/branch_dropdown.tmpl6
-rw-r--r--templates/repo/code/recently_pushed_new_branches.tmpl2
-rw-r--r--templates/repo/commit_load_branches_and_tags.tmpl12
-rw-r--r--templates/repo/commit_page.tmpl66
-rw-r--r--templates/repo/commits.tmpl2
-rw-r--r--templates/repo/commits_list.tmpl16
-rw-r--r--templates/repo/commits_list_small.tmpl6
-rw-r--r--templates/repo/commits_table.tmpl4
-rw-r--r--templates/repo/create.tmpl2
-rw-r--r--templates/repo/diff/blob_excerpt.tmpl6
-rw-r--r--templates/repo/diff/box.tmpl44
-rw-r--r--templates/repo/diff/comment_form.tmpl6
-rw-r--r--templates/repo/diff/comments.tmpl6
-rw-r--r--templates/repo/diff/compare.tmpl22
-rw-r--r--templates/repo/diff/conversation.tmpl20
-rw-r--r--templates/repo/diff/new_review.tmpl2
-rw-r--r--templates/repo/diff/section_split.tmpl8
-rw-r--r--templates/repo/diff/section_unified.tmpl2
-rw-r--r--templates/repo/diff/stats.tmpl2
-rw-r--r--templates/repo/diff/whitespace_dropdown.tmpl8
-rw-r--r--templates/repo/editor/commit_form.tmpl4
-rw-r--r--templates/repo/editor/edit.tmpl2
-rw-r--r--templates/repo/editor/patch.tmpl4
-rw-r--r--templates/repo/empty.tmpl2
-rw-r--r--templates/repo/file_info.tmpl4
-rw-r--r--templates/repo/find/files.tmpl4
-rw-r--r--templates/repo/flags.tmpl2
-rw-r--r--templates/repo/forks.tmpl4
-rw-r--r--templates/repo/graph.tmpl16
-rw-r--r--templates/repo/graph/commits.tmpl10
-rw-r--r--templates/repo/header.tmpl4
-rw-r--r--templates/repo/header_fork.tmpl4
-rw-r--r--templates/repo/home.tmpl22
-rw-r--r--templates/repo/issue/branch_selector_field.tmpl6
-rw-r--r--templates/repo/issue/card.tmpl20
-rw-r--r--templates/repo/issue/fields/checkboxes.tmpl4
-rw-r--r--templates/repo/issue/fields/dropdown.tmpl2
-rw-r--r--templates/repo/issue/fields/input.tmpl2
-rw-r--r--templates/repo/issue/fields/markdown.tmpl2
-rw-r--r--templates/repo/issue/fields/textarea.tmpl6
-rw-r--r--templates/repo/issue/filter_actions.tmpl4
-rw-r--r--templates/repo/issue/filter_list.tmpl8
-rw-r--r--templates/repo/issue/filters.tmpl2
-rw-r--r--templates/repo/issue/labels.tmpl2
-rw-r--r--templates/repo/issue/labels/edit_delete_label.tmpl4
-rw-r--r--templates/repo/issue/labels/label.tmpl2
-rw-r--r--templates/repo/issue/labels/label_list.tmpl2
-rw-r--r--templates/repo/issue/labels/labels_selector_field.tmpl2
-rw-r--r--templates/repo/issue/labels/labels_sidebar.tmpl2
-rw-r--r--templates/repo/issue/list.tmpl4
-rw-r--r--templates/repo/issue/milestone/filter_list.tmpl2
-rw-r--r--templates/repo/issue/milestone/select_menu.tmpl4
-rw-r--r--templates/repo/issue/milestone_issues.tmpl6
-rw-r--r--templates/repo/issue/milestones.tmpl4
-rw-r--r--templates/repo/issue/navbar.tmpl2
-rw-r--r--templates/repo/issue/new_form.tmpl30
-rw-r--r--templates/repo/issue/openclose.tmpl8
-rw-r--r--templates/repo/issue/search.tmpl2
-rw-r--r--templates/repo/issue/view_content.tmpl6
-rw-r--r--templates/repo/issue/view_content/attachments.tmpl4
-rw-r--r--templates/repo/issue/view_content/comments.tmpl10
-rw-r--r--templates/repo/issue/view_content/conversation.tmpl34
-rw-r--r--templates/repo/issue/view_content/pull.tmpl4
-rw-r--r--templates/repo/issue/view_content/pull_merge_instruction.tmpl12
-rw-r--r--templates/repo/issue/view_content/sidebar.tmpl92
-rw-r--r--templates/repo/issue/view_content/watching.tmpl4
-rw-r--r--templates/repo/issue/view_title.tmpl20
-rw-r--r--templates/repo/latest_commit.tmpl6
-rw-r--r--templates/repo/migrate/gitea.tmpl2
-rw-r--r--templates/repo/migrate/migrate.tmpl6
-rw-r--r--templates/repo/migrate/migrating.tmpl6
-rw-r--r--templates/repo/migrate/options.tmpl4
-rw-r--r--templates/repo/projects/view.tmpl2
-rw-r--r--templates/repo/pulls/tab_menu.tmpl2
-rw-r--r--templates/repo/pulse.tmpl12
-rw-r--r--templates/repo/release/list.tmpl19
-rw-r--r--templates/repo/release/new.tmpl6
-rw-r--r--templates/repo/release_tag_header.tmpl2
-rw-r--r--templates/repo/settings/branches.tmpl2
-rw-r--r--templates/repo/settings/deploy_keys.tmpl2
-rw-r--r--templates/repo/settings/githook_edit.tmpl2
-rw-r--r--templates/repo/settings/githooks.tmpl6
-rw-r--r--templates/repo/settings/lfs.tmpl2
-rw-r--r--templates/repo/settings/lfs_file.tmpl2
-rw-r--r--templates/repo/settings/lfs_pointers.tmpl4
-rw-r--r--templates/repo/settings/options.tmpl10
-rw-r--r--templates/repo/settings/protected_branch.tmpl10
-rw-r--r--templates/repo/settings/units/issues.tmpl4
-rw-r--r--templates/repo/settings/units/wiki.tmpl2
-rw-r--r--templates/repo/settings/webhook/base_list.tmpl8
-rw-r--r--templates/repo/settings/webhook/history.tmpl2
-rw-r--r--templates/repo/settings/webhook/link_menu.tmpl54
-rw-r--r--templates/repo/settings/webhook/settings.tmpl2
-rw-r--r--templates/repo/sub_menu.tmpl4
-rw-r--r--templates/repo/tag/list.tmpl21
-rw-r--r--templates/repo/tag/verification_box.tmpl27
-rw-r--r--templates/repo/tag/verification_line.tmpl80
-rw-r--r--templates/repo/view_file.tmpl14
-rw-r--r--templates/repo/view_list.tmpl2
-rw-r--r--templates/repo/wiki/new.tmpl2
-rw-r--r--templates/repo/wiki/revision.tmpl2
-rw-r--r--templates/repo/wiki/start.tmpl2
-rw-r--r--templates/repo/wiki/view.tmpl2
-rw-r--r--templates/shared/actions/runner_edit.tmpl8
-rw-r--r--templates/shared/combomarkdowneditor.tmpl2
-rw-r--r--templates/shared/issuelist.tmpl4
-rw-r--r--templates/shared/repo_search.tmpl2
-rw-r--r--templates/shared/search/code/results.tmpl6
-rw-r--r--templates/shared/searchbottom.tmpl6
-rw-r--r--templates/shared/secrets/add_list.tmpl2
-rw-r--r--templates/shared/user/authorlink.tmpl2
-rw-r--r--templates/shared/user/blocked_users.tmpl83
-rw-r--r--templates/shared/user/profile_big_avatar.tmpl6
-rw-r--r--templates/shared/variables/variable_list.tmpl4
-rw-r--r--templates/status/500.tmpl8
-rw-r--r--templates/swagger/v1_json.tmpl3
-rw-r--r--templates/user/auth/link_account.tmpl2
-rw-r--r--templates/user/auth/signin_inner.tmpl4
-rw-r--r--templates/user/auth/signin_navbar.tmpl2
-rw-r--r--templates/user/auth/signup_inner.tmpl4
-rw-r--r--templates/user/auth/signup_openid_navbar.tmpl2
-rw-r--r--templates/user/auth/webauthn.tmpl2
-rw-r--r--templates/user/auth/webauthn_error.tmpl6
-rw-r--r--templates/user/dashboard/feeds.tmpl2
-rw-r--r--templates/user/dashboard/issues.tmpl6
-rw-r--r--templates/user/dashboard/milestones.tmpl8
-rw-r--r--templates/user/dashboard/navbar.tmpl8
-rw-r--r--templates/user/notification/notification_div.tmpl32
-rw-r--r--templates/user/notification/notification_subscriptions.tmpl4
-rw-r--r--templates/user/overview/package_versions.tmpl2
-rw-r--r--templates/user/overview/packages.tmpl2
-rw-r--r--templates/user/profile.tmpl2
-rw-r--r--templates/user/settings/account.tmpl4
-rw-r--r--templates/user/settings/applications.tmpl18
-rw-r--r--templates/user/settings/applications_oauth2_edit_form.tmpl2
-rw-r--r--templates/user/settings/applications_oauth2_list.tmpl4
-rw-r--r--templates/user/settings/grants_oauth2.tmpl2
-rw-r--r--templates/user/settings/keys_gpg.tmpl2
-rw-r--r--templates/user/settings/keys_principal.tmpl2
-rw-r--r--templates/user/settings/keys_ssh.tmpl2
-rw-r--r--templates/user/settings/profile.tmpl8
-rw-r--r--templates/user/settings/repos.tmpl20
-rw-r--r--templates/webhook/new.tmpl2
-rw-r--r--templates/webhook/new/feishu.tmpl4
-rw-r--r--tests/e2e/e2e_test.go2
-rw-r--r--tests/e2e/example.test.e2e.js2
-rw-r--r--tests/e2e/utils_e2e.js2
-rw-r--r--tests/integration/api_packages_generic_test.go4
-rw-r--r--tests/integration/cmd_forgejo_actions_test.go32
-rw-r--r--tests/integration/cmd_forgejo_test.go36
-rw-r--r--tests/integration/cmd_keys_test.go27
-rw-r--r--tests/integration/git_test.go16
-rw-r--r--tests/integration/integration_test.go38
-rw-r--r--tests/integration/pull_merge_test.go17
-rw-r--r--tests/integration/pull_review_test.go163
-rw-r--r--tests/integration/repo_signed_tag_test.go107
-rwxr-xr-xtools/lint-templates-svg.js26
-rw-r--r--web_src/css/base.css580
-rw-r--r--web_src/css/chroma/base.css4
-rw-r--r--web_src/css/dashboard.css29
-rw-r--r--web_src/css/explore.css6
-rw-r--r--web_src/css/features/projects.css3
-rw-r--r--web_src/css/helpers.css109
-rw-r--r--web_src/css/index.css19
-rw-r--r--web_src/css/markup/content.css3
-rw-r--r--web_src/css/markup/filepreview.css41
-rw-r--r--web_src/css/modules/animations.css1
-rw-r--r--web_src/css/modules/button.css16
-rw-r--r--web_src/css/modules/container.css78
-rw-r--r--web_src/css/modules/grid.css18
-rw-r--r--web_src/css/modules/header.css171
-rw-r--r--web_src/css/modules/label.css294
-rw-r--r--web_src/css/modules/message.css12
-rw-r--r--web_src/css/modules/navbar.css4
-rw-r--r--web_src/css/modules/segment.css195
-rw-r--r--web_src/css/modules/table.css385
-rw-r--r--web_src/css/repo.css58
-rw-r--r--web_src/css/repo/header.css12
-rw-r--r--web_src/css/repo/issue-card.css4
-rw-r--r--web_src/css/repo/issue-list.css8
-rw-r--r--web_src/css/repo/linebutton.css11
-rw-r--r--web_src/css/review.css3
-rw-r--r--web_src/css/shared/flex-list.css2
-rw-r--r--web_src/css/themes/theme-forgejo-dark.css8
-rw-r--r--web_src/css/themes/theme-gitea-dark.css14
-rw-r--r--web_src/css/themes/theme-gitea-light.css10
-rw-r--r--web_src/css/user.css10
-rw-r--r--web_src/fomantic/build/semantic.css4036
-rw-r--r--web_src/fomantic/semantic.json7
-rw-r--r--web_src/js/components/ContextPopup.test.js39
-rw-r--r--web_src/js/components/ContextPopup.vue20
-rw-r--r--web_src/js/components/DashboardRepoList.vue42
-rw-r--r--web_src/js/components/DiffCommitSelector.vue8
-rw-r--r--web_src/js/components/DiffFileList.vue10
-rw-r--r--web_src/js/components/DiffFileTree.vue2
-rw-r--r--web_src/js/components/PullRequestMergeForm.vue6
-rw-r--r--web_src/js/components/RepoActionView.vue23
-rw-r--r--web_src/js/components/RepoBranchTagSelector.vue18
-rw-r--r--web_src/js/components/RepoCodeFrequency.vue2
-rw-r--r--web_src/js/components/RepoContributors.vue4
-rw-r--r--web_src/js/components/RepoRecentCommits.vue2
-rw-r--r--web_src/js/components/ScopedAccessTokenSelector.vue2
-rw-r--r--web_src/js/features/admin/common.js204
-rw-r--r--web_src/js/features/admin/users.js41
-rw-r--r--web_src/js/features/autofocus-end.js6
-rw-r--r--web_src/js/features/common-global.js4
-rw-r--r--web_src/js/features/comp/ComboMarkdownEditor.js33
-rw-r--r--web_src/js/features/comp/LabelEdit.js26
-rw-r--r--web_src/js/features/comp/ReactionSelector.js8
-rw-r--r--web_src/js/features/comp/SearchUserBox.js2
-rw-r--r--web_src/js/features/notification.js6
-rw-r--r--web_src/js/features/org-team.js4
-rw-r--r--web_src/js/features/repo-branch.js50
-rw-r--r--web_src/js/features/repo-code.js114
-rw-r--r--web_src/js/features/repo-common.js2
-rw-r--r--web_src/js/features/repo-diff-commit.js2
-rw-r--r--web_src/js/features/repo-diff.js11
-rw-r--r--web_src/js/features/repo-editor.js14
-rw-r--r--web_src/js/features/repo-findfile.js4
-rw-r--r--web_src/js/features/repo-graph.js12
-rw-r--r--web_src/js/features/repo-home.js58
-rw-r--r--web_src/js/features/repo-issue-content.js4
-rw-r--r--web_src/js/features/repo-issue-list.js4
-rw-r--r--web_src/js/features/repo-issue.js186
-rw-r--r--web_src/js/features/repo-legacy.js175
-rw-r--r--web_src/js/features/repo-projects.js6
-rw-r--r--web_src/js/features/repo-settings.js2
-rw-r--r--web_src/js/features/repo-unicode-escape.js4
-rw-r--r--web_src/js/features/tribute.js2
-rw-r--r--web_src/js/features/user-settings.js2
-rw-r--r--web_src/js/index.js2
-rw-r--r--web_src/js/markup/mermaid.js4
-rw-r--r--web_src/js/modules/fomantic/dropdown.js122
-rw-r--r--web_src/js/modules/fomantic/modal.js4
-rw-r--r--web_src/js/modules/sortable.js19
-rw-r--r--web_src/js/svg.js4
-rw-r--r--web_src/js/utils/dom.js8
-rw-r--r--web_src/js/vitest.setup.js1
-rw-r--r--web_src/js/webcomponents/overflow-menu.js19
-rw-r--r--webpack.config.js1
466 files changed, 7696 insertions, 7984 deletions
diff --git a/.deadcode-out b/.deadcode-out
index c63fd9e0af..1b65e3203d 100644
--- a/.deadcode-out
+++ b/.deadcode-out
@@ -341,5 +341,4 @@ package "code.gitea.io/gitea/services/repository/files"
package "code.gitea.io/gitea/services/webhook"
func NewNotifier
- func List
diff --git a/.dockerignore b/.dockerignore
index 98ef522331..4c14a94620 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -14,7 +14,7 @@ _test
# MS VSCode
.vscode
-__debug_bin
+__debug_bin*
# Architecture specific extensions/prefixes
*.[568vq]
diff --git a/.eslintrc.yaml b/.eslintrc.yaml
index eeb3e20cb8..99ce2e97d6 100644
--- a/.eslintrc.yaml
+++ b/.eslintrc.yaml
@@ -167,7 +167,7 @@ rules:
"@stylistic/js/semi-spacing": [2, {before: false, after: true}]
"@stylistic/js/semi-style": [2, last]
"@stylistic/js/space-before-blocks": [2, always]
- "@stylistic/js/space-before-function-paren": [0]
+ "@stylistic/js/space-before-function-paren": [2, {anonymous: ignore, named: never, asyncArrow: always}]
"@stylistic/js/space-in-parens": [2, never]
"@stylistic/js/space-infix-ops": [2]
"@stylistic/js/space-unary-ops": [2]
@@ -281,7 +281,7 @@ rules:
jquery/no-ajax-events: [2]
jquery/no-ajax: [2]
jquery/no-animate: [2]
- jquery/no-attr: [0]
+ jquery/no-attr: [2]
jquery/no-bind: [2]
jquery/no-class: [0]
jquery/no-clone: [2]
@@ -303,7 +303,7 @@ rules:
jquery/no-in-array: [2]
jquery/no-is-array: [2]
jquery/no-is-function: [2]
- jquery/no-is: [0]
+ jquery/no-is: [2]
jquery/no-load: [2]
jquery/no-map: [2]
jquery/no-merge: [2]
@@ -397,7 +397,7 @@ rules:
no-jquery/no-animate-toggle: [2]
no-jquery/no-animate: [2]
no-jquery/no-append-html: [2]
- no-jquery/no-attr: [0]
+ no-jquery/no-attr: [2]
no-jquery/no-bind: [2]
no-jquery/no-box-model: [2]
no-jquery/no-browser: [2]
@@ -440,7 +440,7 @@ rules:
no-jquery/no-is-numeric: [2]
no-jquery/no-is-plain-object: [2]
no-jquery/no-is-window: [2]
- no-jquery/no-is: [0]
+ no-jquery/no-is: [2]
no-jquery/no-jquery-constructor: [0]
no-jquery/no-live: [2]
no-jquery/no-load-shorthand: [2]
diff --git a/.forgejo/workflows/backport.yml b/.forgejo/workflows/backport.yml
index de6e987ab2..c41259d8d0 100644
--- a/.forgejo/workflows/backport.yml
+++ b/.forgejo/workflows/backport.yml
@@ -33,22 +33,18 @@ jobs:
if: >
!startsWith(vars.ROLE, 'forgejo-') && (
github.event.pull_request.merged
- && (
- (
- github.event.action == 'closed' &&
- contains(toJSON(github.event.pull_request.labels), 'backport/v')
- )
- ||
- (
- github.event.action == 'labeled' &&
- contains(github.event.label.name, 'backport/v')
- )
- )
+ &&
+ contains(toJSON(github.event.pull_request.labels), 'backport/v')
)
runs-on: docker
container:
image: 'docker.io/node:20-bookworm'
steps:
+ - name: event info
+ run: |
+ cat <<'EOF'
+ ${{ toJSON(github) }}
+ EOF
- name: Fetch labels
id: fetch-labels
shell: bash
@@ -77,9 +73,11 @@ jobs:
echo "target-branch: ${{ fromJSON(steps.extract-targets.outputs.targets)[0] }}"
echo "pull-request: ${{ github.event.pull_request.url }}"
- - uses: https://code.forgejo.org/actions/git-backporting@v4.5.2
+ - uses: https://code.forgejo.org/forgejo/git-backporting@23967a28fa7fed4c139e224e905d99b26fc42e94
with:
target-branch: ${{ fromJSON(steps.extract-targets.outputs.targets)[0] }}/forgejo
+ strategy: ort
+ strategy-option: find-renames
no-squash: true
auth: ${{ secrets.BACKPORT_TOKEN }}
pull-request: ${{ github.event.pull_request.url }}
diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml
index 1a5cc15188..182fcd694a 100644
--- a/.forgejo/workflows/renovate.yml
+++ b/.forgejo/workflows/renovate.yml
@@ -1,3 +1,11 @@
+#
+# The 2am run will rebase what needs rebasing & trigger the CI
+# The 4am run will merge one of them
+#
+# These times are chosen to minimize the likelyhood that another PR
+# is merged at the same time. This would not be necessary if automerge
+# worked but as of 30 March 2024 it does not.
+#
name: renovate
on:
@@ -5,7 +13,7 @@ on:
branches:
- 'renovate/**' # self-test updates
schedule:
- - cron: '*/30 * * * *'
+ - cron: '0 2,4 * * *'
env:
RENOVATE_DRY_RUN: ${{ (github.event_name != 'schedule' && github.ref_name != github.event.repository.default_branch) && 'full' || '' }}
diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml
index f40cf1ed83..125cb798dc 100644
--- a/.forgejo/workflows/testing.yml
+++ b/.forgejo/workflows/testing.yml
@@ -14,6 +14,11 @@ jobs:
container:
image: 'docker.io/node:20-bookworm'
steps:
+ - name: event info
+ run: |
+ cat <<'EOF'
+ ${{ toJSON(github) }}
+ EOF
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4
with:
diff --git a/Makefile b/Makefile
index b1c4864c7a..fec96de982 100644
--- a/Makefile
+++ b/Makefile
@@ -88,8 +88,13 @@ STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null)
ifneq ($(STORED_VERSION),)
FORGEJO_VERSION ?= $(STORED_VERSION)
else
- # drop the "g" prefix prepended by git describe to the commit hash
- FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY}
+ ifneq ($(GITEA_VERSION),)
+ FORGEJO_VERSION ?= $(GITEA_VERSION)
+ FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY}
+ else
+ # drop the "g" prefix prepended by git describe to the commit hash
+ FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY}
+ endif
endif
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//')
FORGEJO_VERSION_MINOR=$(shell echo $(FORGEJO_VERSION) | sed -E -e 's/^([0-9]+\.[0-9]+).*/\1/')
@@ -106,7 +111,12 @@ show-version-minor:
RELEASE_VERSION ?= ${FORGEJO_VERSION}
VERSION ?= ${RELEASE_VERSION}
-LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"
+FORGEJO_VERSION_API ?= ${FORGEJO_VERSION}
+
+show-version-api:
+ @echo ${FORGEJO_VERSION_API}
+
+LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION_API)"
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
@@ -458,7 +468,8 @@ lint-actions:
$(GO) run $(ACTIONLINT_PACKAGE)
.PHONY: lint-templates
-lint-templates: .venv
+lint-templates: .venv node_modules
+ @node tools/lint-templates-svg.js
@poetry run djlint $(shell find templates -type f -iname '*.tmpl')
.PHONY: lint-yaml
@@ -814,6 +825,10 @@ generate-go: $(TAGS_PREREQ)
@echo "Running go generate..."
@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' $(GO_PACKAGES)
+.PHONY: merge-locales
+merge-locales:
+ @echo "NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY"
+
.PHONY: security-check
security-check:
go run $(GOVULNCHECK_PACKAGE) ./...
@@ -995,7 +1010,7 @@ generate-gitignore:
.PHONY: generate-images
generate-images: | node_modules
- npm install --no-save fabric@6.0.0-beta19 imagemin-zopfli@7
+ npm install --no-save fabric@6.0.0-beta20 imagemin-zopfli@7
node tools/generate-images.js $(TAGS)
.PHONY: generate-manpage
diff --git a/assets/go-licenses.json b/assets/go-licenses.json
index ad3ab6c4c1..d1062d747b 100644
--- a/assets/go-licenses.json
+++ b/assets/go-licenses.json
@@ -65,11 +65,6 @@
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License."
},
{
- "name": "gitea.com/lunny/dingtalk_webhook",
- "path": "gitea.com/lunny/dingtalk_webhook/LICENSE",
- "licenseText": "Copyright (c) 2016 The Gitea Authors\nCopyright (c) 2015 The Gogs Authors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
- },
- {
"name": "gitea.com/lunny/levelqueue",
"path": "gitea.com/lunny/levelqueue/LICENSE",
"licenseText": "Copyright (c) 2019 Lunny Xiao\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
diff --git a/build/crowdin-to-weblate.sh b/build/crowdin-to-weblate.sh
deleted file mode 100755
index 877b5d9e07..0000000000
--- a/build/crowdin-to-weblate.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-# Copyright 2024 The Forgejo Authors
-# SPDX-License-Identifier: MIT
-
-D=/tmp/crowdin-to-weblate
-mkdir -p $D
-
-function checkout() {
- if test -d $D/gitea ; then
- git -C $D/gitea reset --hard
- return
- fi
-
- git clone --depth 1 https://github.com/go-gitea/gitea $D/gitea
-}
-
-function replace() {
- go run build/merge-forgejo-locales.go $D/gitea/options/locale
- cp -a $D/gitea/options/locale/* options/locale
-}
-
-function run() {
- checkout
- replace
-}
-
-"$@"
diff --git a/build/merge-forgejo-locales.go b/build/merge-forgejo-locales.go
index eebe12578b..05c8d9b5e9 100644
--- a/build/merge-forgejo-locales.go
+++ b/build/merge-forgejo-locales.go
@@ -1,4 +1,4 @@
-// Copyright 2022 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
@@ -7,103 +7,9 @@
package main
import (
- "bufio"
- "log"
- "os"
- "regexp"
- "strings"
+ "fmt"
)
-const (
- trimPrefix = "gitea_"
- sourceFolder = "options/locales/"
-)
-
-// returns list of locales, still containing the file extension!
-func generate_locale_list() []string {
- localeFiles, _ := os.ReadDir(sourceFolder)
- locales := []string{}
- for _, localeFile := range localeFiles {
- if !localeFile.IsDir() && strings.HasPrefix(localeFile.Name(), trimPrefix) {
- locales = append(locales, strings.TrimPrefix(localeFile.Name(), trimPrefix))
- }
- }
- return locales
-}
-
-// replace all occurrences of Gitea with Forgejo
-func renameGiteaForgejo(filename string) []byte {
- file, err := os.Open(filename)
- if err != nil {
- panic(err)
- }
-
- replacements := []string{
- "Gitea", "Forgejo",
- "https://docs.gitea.com/installation/install-from-binary", "https://forgejo.org/download/#installation-from-binary",
- "https://github.com/go-gitea/gitea/tree/master/docker", "https://forgejo.org/download/#container-image",
- "https://docs.gitea.com/installation/install-from-package", "https://forgejo.org/download",
- "https://code.gitea.io/gitea", "https://forgejo.org/download",
- "code.gitea.io/gitea", "Forgejo",
- `<a href="https://github.com/go-gitea/gitea/issues" target="_blank">GitHub</a>`, `<a href="https://codeberg.org/forgejo/forgejo/issues" target="_blank">Codeberg</a>`,
- "https://github.com/go-gitea/gitea", "https://codeberg.org/forgejo/forgejo",
- "https://blog.gitea.io", "https://forgejo.org/news",
- "https://docs.gitea.com/usage/protected-tags", "https://forgejo.org/docs/latest/user/protection/#protected-tags",
- "https://docs.gitea.com/usage/webhooks", "https://forgejo.org/docs/latest/user/webhooks/",
- }
- replacer := strings.NewReplacer(replacements...)
- replaced := make(map[string]bool, len(replacements)/2)
- count_replaced := func(original string) {
- for i := 0; i < len(replacements); i += 2 {
- if strings.Contains(original, replacements[i]) {
- replaced[replacements[i]] = true
- }
- }
- }
-
- out := make([]byte, 0, 1024)
- scanner := bufio.NewScanner(file)
- scanner.Split(bufio.ScanLines)
- for scanner.Scan() {
- line := scanner.Text()
-
- if strings.HasPrefix(line, "license_desc=") {
- line = strings.Replace(line, "GitHub", "Forgejo", 1)
- }
-
- if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
- out = append(out, []byte(line+"\n")...)
- } else if strings.HasPrefix(line, "settings.web_hook_name_gitea") {
- out = append(out, []byte(line+"\n")...)
- out = append(out, []byte("settings.web_hook_name_forgejo = Forgejo\n")...)
- } else if strings.HasPrefix(line, "migrate.gitea.description") {
- re := regexp.MustCompile(`(.*Gitea)`)
- out = append(out, []byte(re.ReplaceAllString(line, "${1}/Forgejo")+"\n")...)
- } else {
- count_replaced(line)
- out = append(out, []byte(replacer.Replace(line)+"\n")...)
- }
- }
- file.Close()
- if strings.HasSuffix(filename, "gitea_en-US.ini") {
- for i := 0; i < len(replacements); i += 2 {
- if replaced[replacements[i]] == false {
- log.Fatalf("%s was never used to replace something in %s, it is obsolete and must be updated", replacements[i], filename)
- }
- }
- }
- return out
-}
-
func main() {
- d := os.Args[1]
- files, err := os.ReadDir(d)
- if err != nil {
- log.Fatal(err)
- }
-
- for _, f := range files {
- p := d + "/" + f.Name()
- os.WriteFile(p, renameGiteaForgejo(p), 0o644)
- }
+ fmt.Println("NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY")
}
diff --git a/cmd/dump.go b/cmd/dump.go
index 6fbd049191..3ea92aa112 100644
--- a/cmd/dump.go
+++ b/cmd/dump.go
@@ -454,14 +454,18 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA
for _, file := range files {
currentAbsPath := filepath.Join(absPath, file.Name())
currentInsidePath := path.Join(insidePath, file.Name())
+
+ if util.SliceContainsString(excludeAbsPath, currentAbsPath) {
+ log.Debug("Skipping %q because matched an excluded path.", currentAbsPath)
+ continue
+ }
+
if file.IsDir() {
- if !util.SliceContainsString(excludeAbsPath, currentAbsPath) {
- if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
- return err
- }
- if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
- return err
- }
+ if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
+ return err
+ }
+ if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
+ return err
}
} else {
// only copy regular files and symlink regular files, skip non-regular files like socket/pipe/...
diff --git a/cmd/dump_test.go b/cmd/dump_test.go
new file mode 100644
index 0000000000..7b83c70f09
--- /dev/null
+++ b/cmd/dump_test.go
@@ -0,0 +1,117 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package cmd
+
+import (
+ "io"
+ "os"
+ "testing"
+
+ "github.com/mholt/archiver/v3"
+ "github.com/stretchr/testify/assert"
+)
+
+type mockArchiver struct {
+ addedFiles []string
+}
+
+func (mockArchiver) Create(out io.Writer) error {
+ return nil
+}
+
+func (m *mockArchiver) Write(f archiver.File) error {
+ m.addedFiles = append(m.addedFiles, f.Name())
+ return nil
+}
+
+func (mockArchiver) Close() error {
+ return nil
+}
+
+func TestAddRecursiveExclude(t *testing.T) {
+ t.Run("Empty", func(t *testing.T) {
+ dir := t.TempDir()
+ archiver := &mockArchiver{}
+
+ err := addRecursiveExclude(archiver, "", dir, []string{}, false)
+ assert.NoError(t, err)
+ assert.Empty(t, archiver.addedFiles)
+ })
+
+ t.Run("Single file", func(t *testing.T) {
+ dir := t.TempDir()
+ err := os.WriteFile(dir+"/example", nil, 0o666)
+ assert.NoError(t, err)
+
+ t.Run("No exclude", func(t *testing.T) {
+ archiver := &mockArchiver{}
+
+ err = addRecursiveExclude(archiver, "", dir, nil, false)
+ assert.NoError(t, err)
+ assert.Len(t, archiver.addedFiles, 1)
+ assert.EqualValues(t, "example", archiver.addedFiles[0])
+ })
+
+ t.Run("With exclude", func(t *testing.T) {
+ archiver := &mockArchiver{}
+
+ err = addRecursiveExclude(archiver, "", dir, []string{dir + "/example"}, false)
+ assert.NoError(t, err)
+ assert.Empty(t, archiver.addedFiles)
+ })
+ })
+
+ t.Run("File inside directory", func(t *testing.T) {
+ dir := t.TempDir()
+ err := os.MkdirAll(dir+"/deep/nested/folder", 0o750)
+ assert.NoError(t, err)
+ err = os.WriteFile(dir+"/deep/nested/folder/example", nil, 0o666)
+ assert.NoError(t, err)
+ err = os.WriteFile(dir+"/deep/nested/folder/another-file", nil, 0o666)
+ assert.NoError(t, err)
+
+ t.Run("No exclude", func(t *testing.T) {
+ archiver := &mockArchiver{}
+
+ err = addRecursiveExclude(archiver, "", dir, nil, false)
+ assert.NoError(t, err)
+ assert.Len(t, archiver.addedFiles, 5)
+ assert.EqualValues(t, "deep", archiver.addedFiles[0])
+ assert.EqualValues(t, "deep/nested", archiver.addedFiles[1])
+ assert.EqualValues(t, "deep/nested/folder", archiver.addedFiles[2])
+ assert.EqualValues(t, "deep/nested/folder/example", archiver.addedFiles[3])
+ assert.EqualValues(t, "deep/nested/folder/another-file", archiver.addedFiles[4])
+ })
+
+ t.Run("Exclude first directory", func(t *testing.T) {
+ archiver := &mockArchiver{}
+
+ err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep"}, false)
+ assert.NoError(t, err)
+ assert.Empty(t, archiver.addedFiles)
+ })
+
+ t.Run("Exclude nested directory", func(t *testing.T) {
+ archiver := &mockArchiver{}
+
+ err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep/nested/folder"}, false)
+ assert.NoError(t, err)
+ assert.Len(t, archiver.addedFiles, 2)
+ assert.EqualValues(t, "deep", archiver.addedFiles[0])
+ assert.EqualValues(t, "deep/nested", archiver.addedFiles[1])
+ })
+
+ t.Run("Exclude file", func(t *testing.T) {
+ archiver := &mockArchiver{}
+
+ err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep/nested/folder/example"}, false)
+ assert.NoError(t, err)
+ assert.Len(t, archiver.addedFiles, 4)
+ assert.EqualValues(t, "deep", archiver.addedFiles[0])
+ assert.EqualValues(t, "deep/nested", archiver.addedFiles[1])
+ assert.EqualValues(t, "deep/nested/folder", archiver.addedFiles[2])
+ assert.EqualValues(t, "deep/nested/folder/another-file", archiver.addedFiles[3])
+ })
+ })
+}
diff --git a/cmd/hook.go b/cmd/hook.go
index 966e4a57ca..48a64fd197 100644
--- a/cmd/hook.go
+++ b/cmd/hook.go
@@ -293,9 +293,32 @@ Forgejo or set your environment appropriately.`, "")
return nil
}
+// runHookUpdate process the update hook: https://git-scm.com/docs/githooks#update
func runHookUpdate(c *cli.Context) error {
- // Update is empty and is kept only for backwards compatibility
- return nil
+ // Now if we're an internal don't do anything else
+ if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
+ return nil
+ }
+
+ ctx, cancel := installSignals()
+ defer cancel()
+
+ // The last three arguments given to the hook are in order: reference name, old commit ID and new commit ID.
+ args := os.Args[len(os.Args)-3:]
+ refFullName := git.RefName(args[0])
+ newCommitID := args[2]
+
+ // Only process pull references.
+ if !refFullName.IsPull() {
+ return nil
+ }
+
+ // Deletion of the ref means that the new commit ID is only composed of '0'.
+ if strings.ContainsFunc(newCommitID, func(e rune) bool { return e != '0' }) {
+ return nil
+ }
+
+ return fail(ctx, fmt.Sprintf("The deletion of %s is skipped as it's an internal reference.", refFullName), "")
}
func runHookPostReceive(c *cli.Context) error {
@@ -583,7 +606,7 @@ Forgejo or set your environment appropriately.`, "")
for {
// note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed
- rs, err = readPktLine(ctx, reader, pktLineTypeUnknow)
+ rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
if err != nil {
return err
}
@@ -604,7 +627,7 @@ Forgejo or set your environment appropriately.`, "")
if hasPushOptions {
for {
- rs, err = readPktLine(ctx, reader, pktLineTypeUnknow)
+ rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
if err != nil {
return err
}
@@ -699,8 +722,8 @@ Forgejo or set your environment appropriately.`, "")
type pktLineType int64
const (
- // UnKnow type
- pktLineTypeUnknow pktLineType = 0
+ // Unknown type
+ pktLineTypeUnknown pktLineType = 0
// flush-pkt "0000"
pktLineTypeFlush pktLineType = iota
// data line
@@ -714,22 +737,16 @@ type gitPktLine struct {
Data []byte
}
+// Reads an Pkt-Line from `in`. If requestType is not unknown, it will a
func readPktLine(ctx context.Context, in *bufio.Reader, requestType pktLineType) (*gitPktLine, error) {
- var (
- err error
- r *gitPktLine
- )
-
- // read prefix
+ // Read length prefix
lengthBytes := make([]byte, 4)
- for i := 0; i < 4; i++ {
- lengthBytes[i], err = in.ReadByte()
- if err != nil {
- return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)
- }
+ if n, err := in.Read(lengthBytes); n != 4 || err != nil {
+ return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)
}
- r = new(gitPktLine)
+ var err error
+ r := &gitPktLine{}
r.Length, err = strconv.ParseUint(string(lengthBytes), 16, 32)
if err != nil {
return nil, fail(ctx, "Protocol: format parse error", "Pkt-Line format is wrong :%v", err)
@@ -748,11 +765,8 @@ func readPktLine(ctx context.Context, in *bufio.Reader, requestType pktLineType)
}
r.Data = make([]byte, r.Length-4)
- for i := range r.Data {
- r.Data[i], err = in.ReadByte()
- if err != nil {
- return nil, fail(ctx, "Protocol: data error", "Pkt-Line: read stdin failed : %v", err)
- }
+ if n, err := io.ReadFull(in, r.Data); uint64(n) != r.Length-4 || err != nil {
+ return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)
}
r.Type = pktLineTypeData
@@ -768,20 +782,23 @@ func writeFlushPktLine(ctx context.Context, out io.Writer) error {
return nil
}
+// Write an Pkt-Line based on `data` to `out` according to the specifcation.
+// https://git-scm.com/docs/protocol-common
func writeDataPktLine(ctx context.Context, out io.Writer, data []byte) error {
- hexchar := []byte("0123456789abcdef")
- hex := func(n uint64) byte {
- return hexchar[(n)&15]
+ // Implementations SHOULD NOT send an empty pkt-line ("0004").
+ if len(data) == 0 {
+ return fail(ctx, "Protocol: write error", "Not allowed to write empty Pkt-Line")
}
length := uint64(len(data) + 4)
- tmp := make([]byte, 4)
- tmp[0] = hex(length >> 12)
- tmp[1] = hex(length >> 8)
- tmp[2] = hex(length >> 4)
- tmp[3] = hex(length)
- lr, err := out.Write(tmp)
+ // The maximum length of a pkt-line’s data component is 65516 bytes.
+ // Implementations MUST NOT send pkt-line whose length exceeds 65520 (65516 bytes of payload + 4 bytes of length data).
+ if length > 65520 {
+ return fail(ctx, "Protocol: write error", "Pkt-Line exceeds maximum of 65520 bytes")
+ }
+
+ lr, err := fmt.Fprintf(out, "%04x", length)
if err != nil || lr != 4 {
return fail(ctx, "Protocol: write error", "Pkt-Line response failed: %v", err)
}
diff --git a/cmd/hook_test.go b/cmd/hook_test.go
index 91f24ff2b4..d4e16dc411 100644
--- a/cmd/hook_test.go
+++ b/cmd/hook_test.go
@@ -14,29 +14,72 @@ import (
)
func TestPktLine(t *testing.T) {
- // test read
ctx := context.Background()
- s := strings.NewReader("0000")
- r := bufio.NewReader(s)
- result, err := readPktLine(ctx, r, pktLineTypeFlush)
- assert.NoError(t, err)
- assert.Equal(t, pktLineTypeFlush, result.Type)
-
- s = strings.NewReader("0006a\n")
- r = bufio.NewReader(s)
- result, err = readPktLine(ctx, r, pktLineTypeData)
- assert.NoError(t, err)
- assert.Equal(t, pktLineTypeData, result.Type)
- assert.Equal(t, []byte("a\n"), result.Data)
-
- // test write
- w := bytes.NewBuffer([]byte{})
- err = writeFlushPktLine(ctx, w)
- assert.NoError(t, err)
- assert.Equal(t, []byte("0000"), w.Bytes())
-
- w.Reset()
- err = writeDataPktLine(ctx, w, []byte("a\nb"))
- assert.NoError(t, err)
- assert.Equal(t, []byte("0007a\nb"), w.Bytes())
+
+ t.Run("Read", func(t *testing.T) {
+ s := strings.NewReader("0000")
+ r := bufio.NewReader(s)
+ result, err := readPktLine(ctx, r, pktLineTypeFlush)
+ assert.NoError(t, err)
+ assert.Equal(t, pktLineTypeFlush, result.Type)
+
+ s = strings.NewReader("0006a\n")
+ r = bufio.NewReader(s)
+ result, err = readPktLine(ctx, r, pktLineTypeData)
+ assert.NoError(t, err)
+ assert.Equal(t, pktLineTypeData, result.Type)
+ assert.Equal(t, []byte("a\n"), result.Data)
+
+ s = strings.NewReader("0004")
+ r = bufio.NewReader(s)
+ result, err = readPktLine(ctx, r, pktLineTypeData)
+ assert.Error(t, err)
+ assert.Nil(t, result)
+
+ data := strings.Repeat("x", 65516)
+ r = bufio.NewReader(strings.NewReader("fff0" + data))
+ result, err = readPktLine(ctx, r, pktLineTypeData)
+ assert.NoError(t, err)
+ assert.Equal(t, pktLineTypeData, result.Type)
+ assert.Equal(t, []byte(data), result.Data)
+
+ r = bufio.NewReader(strings.NewReader("fff1a"))
+ result, err = readPktLine(ctx, r, pktLineTypeData)
+ assert.Error(t, err)
+ assert.Nil(t, result)
+ })
+
+ t.Run("Write", func(t *testing.T) {
+ w := bytes.NewBuffer([]byte{})
+ err := writeFlushPktLine(ctx, w)
+ assert.NoError(t, err)
+ assert.Equal(t, []byte("0000"), w.Bytes())
+
+ w.Reset()
+ err = writeDataPktLine(ctx, w, []byte("a\nb"))
+ assert.NoError(t, err)
+ assert.Equal(t, []byte("0007a\nb"), w.Bytes())
+
+ w.Reset()
+ data := bytes.Repeat([]byte{0x05}, 288)
+ err = writeDataPktLine(ctx, w, data)
+ assert.NoError(t, err)
+ assert.Equal(t, append([]byte("0124"), data...), w.Bytes())
+
+ w.Reset()
+ err = writeDataPktLine(ctx, w, nil)
+ assert.Error(t, err)
+ assert.Empty(t, w.Bytes())
+
+ w.Reset()
+ data = bytes.Repeat([]byte{0x64}, 65516)
+ err = writeDataPktLine(ctx, w, data)
+ assert.NoError(t, err)
+ assert.Equal(t, append([]byte("fff0"), data...), w.Bytes())
+
+ w.Reset()
+ err = writeDataPktLine(ctx, w, bytes.Repeat([]byte{0x64}, 65516+1))
+ assert.Error(t, err)
+ assert.Empty(t, w.Bytes())
+ })
}
diff --git a/cmd/serv.go b/cmd/serv.go
index 9d26515254..d5c54f91b8 100644
--- a/cmd/serv.go
+++ b/cmd/serv.go
@@ -14,6 +14,7 @@ import (
"regexp"
"strconv"
"strings"
+ "testing"
"time"
"unicode"
@@ -106,7 +107,10 @@ func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error
logMsg = userMessage + ". " + logMsg
}
}
- _ = private.SSHLog(ctx, true, logMsg)
+ // Don't send an log if this is done in a test and no InternalToken is set.
+ if !testing.Testing() || setting.InternalToken != "" {
+ _ = private.SSHLog(ctx, true, logMsg)
+ }
}
return cli.Exit("", 1)
}
diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 9853a8e050..74999b5bb3 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -441,7 +441,7 @@ INTERNAL_TOKEN =
;INTERNAL_TOKEN_URI = file:/etc/gitea/internal_token
;;
;; How long to remember that a user is logged in before requiring relogin (in days)
-;LOGIN_REMEMBER_DAYS = 7
+;LOGIN_REMEMBER_DAYS = 31
;;
;; Name of the cookie used to store the current username.
;COOKIE_USERNAME = gitea_awesome
@@ -2338,6 +2338,8 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
;MERMAID_MAX_SOURCE_CHARACTERS = 5000
+;; Set the maximum number of lines allowed for a filepreview. (Set to -1 to disable limits; set to 0 to disable the feature)
+;FILEPREVIEW_MAX_LINES = 50
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md
index 8ca1dd3b2b..44c875761d 100644
--- a/docs/content/administration/config-cheat-sheet.en-us.md
+++ b/docs/content/administration/config-cheat-sheet.en-us.md
@@ -528,7 +528,7 @@ And the following unique queues:
- `INSTALL_LOCK`: **false**: Controls access to the installation page. When set to "true", the installation page is not accessible.
- `SECRET_KEY`: **\<random at every install\>**: Global secret key. This key is VERY IMPORTANT, if you lost it, the data encrypted by it (like 2FA secret) can't be decrypted anymore.
- `SECRET_KEY_URI`: **_empty_**: Instead of defining SECRET_KEY, this option can be used to use the key stored in a file (example value: `file:/etc/gitea/secret_key`). It shouldn't be lost like SECRET_KEY.
-- `LOGIN_REMEMBER_DAYS`: **7**: Cookie lifetime, in days.
+- `LOGIN_REMEMBER_DAYS`: **31**: How long to remember that a user is logged in before requiring relogin (in days).
- `COOKIE_REMEMBER_NAME`: **gitea\_incredible**: Name of cookie used to store authentication
information.
- `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**: Header name for reverse proxy
diff --git a/docs/content/administration/config-cheat-sheet.zh-cn.md b/docs/content/administration/config-cheat-sheet.zh-cn.md
index f636927da6..19b1b6c774 100644
--- a/docs/content/administration/config-cheat-sheet.zh-cn.md
+++ b/docs/content/administration/config-cheat-sheet.zh-cn.md
@@ -507,7 +507,7 @@ Gitea 创建以下éžå”¯ä¸€é˜Ÿåˆ—:
- `INSTALL_LOCK`: **false**:控制是å¦èƒ½å¤Ÿè®¿é—®å®‰è£…å‘导页é¢ï¼Œè®¾ç½®ä¸º `true` 则ç¦æ­¢è®¿é—®å®‰è£…å‘导页é¢ã€‚
- `SECRET_KEY`: **\<æ¯æ¬¡å®‰è£…æ—¶éšæœºç”Ÿæˆ\>**:全局æœåŠ¡å™¨å®‰å…¨å¯†é’¥ã€‚这个密钥éžå¸¸é‡è¦ï¼Œå¦‚果丢失将无法解密加密的数æ®ï¼ˆä¾‹å¦‚ 2FA)。
- `SECRET_KEY_URI`: **_empty_**:与定义 `SECRET_KEY` ä¸åŒï¼Œæ­¤é€‰é¡¹å¯ç”¨äºŽä½¿ç”¨å­˜å‚¨åœ¨æ–‡ä»¶ä¸­çš„密钥(示例值:`file:/etc/gitea/secret_key`)。它ä¸åº”è¯¥åƒ `SECRET_KEY` 一样容易丢失。
-- `LOGIN_REMEMBER_DAYS`: **7**:Cookie ä¿å­˜æ—¶é—´ï¼Œå•ä½ä¸ºå¤©ã€‚
+- `LOGIN_REMEMBER_DAYS`: **31**:在è¦æ±‚é‡æ–°ç™»å½•ä¹‹å‰ï¼Œè®°ä½ç”¨æˆ·çš„登录状æ€å¤šé•¿æ—¶é—´ï¼ˆä»¥å¤©ä¸ºå•ä½ï¼‰ã€‚
- `COOKIE_REMEMBER_NAME`: **gitea\_incredible**:ä¿å­˜è‡ªåŠ¨ç™»å½•ä¿¡æ¯çš„ Cookie å称。
- `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**:åå‘代ç†è®¤è¯çš„ HTTP 头部å称,用于æ供用户信æ¯ã€‚
- `REVERSE_PROXY_AUTHENTICATION_EMAIL`: **X-WEBAUTH-EMAIL**:åå‘代ç†è®¤è¯çš„ HTTP 头部å称,用于æ供邮箱信æ¯ã€‚
diff --git a/docs/content/contributing/guidelines-frontend.en-us.md b/docs/content/contributing/guidelines-frontend.en-us.md
index b19101dd47..611b93e5b3 100644
--- a/docs/content/contributing/guidelines-frontend.en-us.md
+++ b/docs/content/contributing/guidelines-frontend.en-us.md
@@ -47,7 +47,7 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h
9. Avoid unnecessary `!important` in CSS, add comments to explain why it's necessary if it can't be avoided.
10. Avoid mixing different events in one event listener, prefer to use individual event listeners for every event.
11. Custom event names are recommended to use `ce-` prefix.
-12. Prefer using Tailwind CSS which is available via `tw-` prefix, e.g. `tw-relative`. Gitea's helper CSS classes use `gt-` prefix (`gt-mono`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`).
+12. Prefer using Tailwind CSS which is available via `tw-` prefix, e.g. `tw-relative`. Gitea's helper CSS classes use `gt-` prefix (`gt-word-break`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`).
13. Avoid inline scripts & styles as much as possible, it's recommended to put JS code into JS files and use CSS classes. If inline scripts & styles are unavoidable, explain the reason why it can't be avoided.
### Accessibility / ARIA
@@ -118,7 +118,7 @@ However, there are still some special cases, so the current guideline is:
### Show/Hide Elements
* Vue components are recommended to use `v-if` and `v-show` to show/hide elements.
-* Go template code should use Gitea's `.gt-hidden` and `showElem()/hideElem()/toggleElem()`, see more details in `.gt-hidden`'s comment.
+* Go template code should use `.tw-hidden` and `showElem()/hideElem()/toggleElem()`, see more details in `.tw-hidden`'s comment.
### Styles and Attributes in Go HTML Template
diff --git a/docs/content/contributing/guidelines-frontend.zh-cn.md b/docs/content/contributing/guidelines-frontend.zh-cn.md
index 961a331ac2..1df7bc5bcf 100644
--- a/docs/content/contributing/guidelines-frontend.zh-cn.md
+++ b/docs/content/contributing/guidelines-frontend.zh-cn.md
@@ -47,7 +47,7 @@ HTML 页é¢ç”±[Go HTML Template](https://pkg.go.dev/html/template)渲染。
9. é¿å…在 CSS 中使用ä¸å¿…è¦çš„`!important`,如果无法é¿å…,添加注释解释为什么需è¦å®ƒã€‚
10. é¿å…在一个事件监å¬å™¨ä¸­æ··åˆä¸åŒçš„事件,优先为æ¯ä¸ªäº‹ä»¶ä½¿ç”¨ç‹¬ç«‹çš„事件监å¬å™¨ã€‚
11. 推è使用自定义事件å称å‰ç¼€`ce-`。
-12. 建议使用 Tailwind CSS,它å¯ä»¥é€šè¿‡ `tw-` å‰ç¼€èŽ·å¾—,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` å‰ç¼€ï¼ˆ`gt-mono`),Gitea 自身的ç§æœ‰æ¡†æž¶çº§ CSS 类使用 `g-` å‰ç¼€ï¼ˆ`g-modal-confirm`)。
+12. 建议使用 Tailwind CSS,它å¯ä»¥é€šè¿‡ `tw-` å‰ç¼€èŽ·å¾—,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` å‰ç¼€ï¼ˆ`gt-word-break`),Gitea 自身的ç§æœ‰æ¡†æž¶çº§ CSS 类使用 `g-` å‰ç¼€ï¼ˆ`g-modal-confirm`)。
13. å°½é‡é¿å…内è”脚本和样å¼ï¼Œå»ºè®®å°†JS代ç æ”¾å…¥JS文件中并使用CSS类。如果内è”脚本和样å¼ä¸å¯é¿å…,请解释无法é¿å…的原因。
### å¯è®¿é—®æ€§ / ARIA
@@ -117,7 +117,7 @@ Gitea使用一些补ä¸ä½¿Fomantic UIæ›´å…·å¯è®¿é—®æ€§ï¼ˆå‚è§`aria.js`å’Œ`ari
### 显示/éšè—元素
* 推è在Vue组件中使用`v-if`å’Œ`v-show`æ¥æ˜¾ç¤º/éšè—元素。
-* Go 模æ¿ä»£ç åº”使用 Gitea çš„ `.gt-hidden` å’Œ `showElem()/hideElem()/toggleElem()` æ¥æ˜¾ç¤º/éšè—元素,请å‚阅`.gt-hidden`的注释以获å–更多详细信æ¯ã€‚
+* Go 模æ¿ä»£ç åº”使用 `.tw-hidden` å’Œ `showElem()/hideElem()/toggleElem()` æ¥æ˜¾ç¤º/éšè—元素,请å‚阅`.tw-hidden`的注释以获å–更多详细信æ¯ã€‚
### Go HTML 模æ¿ä¸­çš„æ ·å¼å’Œå±žæ€§
diff --git a/go.mod b/go.mod
index dbefea608c..a0b94dcfda 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,6 @@ require (
gitea.com/go-chi/cache v0.2.0
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
- gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
@@ -32,7 +31,7 @@ require (
github.com/editorconfig/editorconfig-core-go/v2 v2.6.1
github.com/emersion/go-imap v1.2.1
github.com/emirpasic/gods v1.18.1
- github.com/felixge/fgprof v0.9.3
+ github.com/felixge/fgprof v0.9.4
github.com/fsnotify/fsnotify v1.7.0
github.com/gliderlabs/ssh v0.3.7
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
@@ -45,7 +44,7 @@ require (
github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.11.0
github.com/go-ldap/ldap/v3 v3.4.6
- github.com/go-sql-driver/mysql v1.8.0
+ github.com/go-sql-driver/mysql v1.8.1
github.com/go-swagger/go-swagger v0.30.5
github.com/go-testfixtures/testfixtures/v3 v3.10.0
github.com/go-webauthn/webauthn v0.10.0
@@ -54,7 +53,7 @@ require (
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/google/go-github/v57 v57.0.0
- github.com/google/pprof v0.0.0-20240117000934-35fc243c5815
+ github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7
github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.1.2
github.com/gorilla/sessions v1.2.2
@@ -75,13 +74,13 @@ require (
github.com/meilisearch/meilisearch-go v0.26.1
github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.26
- github.com/minio/minio-go/v7 v7.0.66
+ github.com/minio/minio-go/v7 v7.0.69
github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.52
github.com/niklasfasching/go-org v1.7.0
github.com/olivere/elastic/v7 v7.0.32
github.com/opencontainers/go-digest v1.0.0
- github.com/opencontainers/image-spec v1.1.0-rc6
+ github.com/opencontainers/image-spec v1.1.0
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.18.0
github.com/quasoft/websspi v1.1.2
diff --git a/go.sum b/go.sum
index ae1f89b43f..b1619b8b67 100644
--- a/go.sum
+++ b/go.sum
@@ -62,8 +62,6 @@ gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk=
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96 h1:IFDiMBObsP6CZIRaDLd54SR6zPYAffPXiXck5Xslu0Q=
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96/go.mod h1:0iEpFKnwO5dG0aF98O4eq6FMsAiXkNBaDIlUOlq4BtM=
-gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw=
-gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY=
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
@@ -188,9 +186,15 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0=
+github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
+github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
+github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
@@ -258,8 +262,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
-github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
-github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
+github.com/felixge/fgprof v0.9.4 h1:ocDNwMFlnA0NU0zSB3I52xkO4sFXk80VK9lXjLClu88=
+github.com/felixge/fgprof v0.9.4/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@@ -338,8 +342,8 @@ github.com/go-openapi/validate v0.22.6 h1:+NhuwcEYpWdO5Nm4bmvhGLW0rt1Fcc532Mu3wp
github.com/go-openapi/validate v0.22.6/go.mod h1:eaddXSqKeTg5XpSmj1dYyFTK/95n/XHwcOY+BMxKMyM=
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
-github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
-github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
+github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
+github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-swagger/go-swagger v0.30.5 h1:SQ2+xSonWjjoEMOV5tcOnZJVlfyUfCBhGQGArS1b9+U=
github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewetCdvEXqgPvbc/Q=
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0=
@@ -355,6 +359,9 @@ github.com/go-webauthn/x v0.1.6 h1:QNAX+AWeqRt9loE8mULeWJCqhVG5D/jvdmJ47fIWCkQ=
github.com/go-webauthn/x v0.1.6/go.mod h1:W8dFVZ79o4f+nY1eOUICy/uq5dhrRl7mxQkYhXTo0FA=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
+github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
+github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
@@ -443,9 +450,8 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
-github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8=
-github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
+github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
+github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -496,7 +502,7 @@ github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
+github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
@@ -569,6 +575,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
@@ -616,8 +623,8 @@ github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
-github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw=
-github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs=
+github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
+github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
@@ -670,8 +677,9 @@ github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU=
-github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
+github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
+github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
+github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
@@ -1036,9 +1044,9 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/models/asymkey/gpg_key_commit_verification.go b/models/asymkey/gpg_key_commit_verification.go
index 83fbab5d36..9aa606405e 100644
--- a/models/asymkey/gpg_key_commit_verification.go
+++ b/models/asymkey/gpg_key_commit_verification.go
@@ -5,18 +5,10 @@ package asymkey
import (
"context"
- "fmt"
- "hash"
- "strings"
- "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
-
- "github.com/keybase/go-crypto/openpgp/packet"
)
// __________________ ________ ____ __.
@@ -40,45 +32,22 @@ import (
// This file provides functions relating commit verification
-// CommitVerification represents a commit validation of signature
-type CommitVerification struct {
- Verified bool
- Warning bool
- Reason string
- SigningUser *user_model.User
- CommittingUser *user_model.User
- SigningEmail string
- SigningKey *GPGKey
- SigningSSHKey *PublicKey
- TrustStatus string
-}
-
// SignCommit represents a commit with validation of signature.
type SignCommit struct {
- Verification *CommitVerification
+ Verification *ObjectVerification
*user_model.UserCommit
}
-const (
- // BadSignature is used as the reason when the signature has a KeyID that is in the db
- // but no key that has that ID verifies the signature. This is a suspicious failure.
- BadSignature = "gpg.error.probable_bad_signature"
- // BadDefaultSignature is used as the reason when the signature has a KeyID that matches the
- // default Key but is not verified by the default key. This is a suspicious failure.
- BadDefaultSignature = "gpg.error.probable_bad_default_signature"
- // NoKeyFound is used as the reason when no key can be found to verify the signature.
- NoKeyFound = "gpg.error.no_gpg_keys_found"
-)
-
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) []*SignCommit {
newCommits := make([]*SignCommit, 0, len(oldCommits))
keyMap := map[string]bool{}
for _, c := range oldCommits {
+ o := commitToGitObject(c.Commit)
signCommit := &SignCommit{
UserCommit: c,
- Verification: ParseCommitWithSignature(ctx, c.Commit),
+ Verification: ParseObjectWithSignature(ctx, &o),
}
_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
@@ -88,456 +57,7 @@ func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.Use
return newCommits
}
-// ParseCommitWithSignature check if signature is good against keystore.
-func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerification {
- var committer *user_model.User
- if c.Committer != nil {
- var err error
- // Find Committer account
- committer, err = user_model.GetUserByEmail(ctx, c.Committer.Email) // This finds the user by primary email or activated email so commit will not be valid if email is not
- if err != nil { // Skipping not user for committer
- committer = &user_model.User{
- Name: c.Committer.Name,
- Email: c.Committer.Email,
- }
- // We can expect this to often be an ErrUserNotExist. in the case
- // it is not, however, it is important to log it.
- if !user_model.IsErrUserNotExist(err) {
- log.Error("GetUserByEmail: %v", err)
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false,
- Reason: "gpg.error.no_committer_account",
- }
- }
-
- }
- }
-
- // If no signature just report the committer
- if c.Signature == nil {
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false, // Default value
- Reason: "gpg.error.not_signed_commit", // Default value
- }
- }
-
- // If this a SSH signature handle it differently
- if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") {
- return ParseCommitWithSSHSignature(ctx, c, committer)
- }
-
- // Parsing signature
- sig, err := extractSignature(c.Signature.Signature)
- if err != nil { // Skipping failed to extract sign
- log.Error("SignatureRead err: %v", err)
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false,
- Reason: "gpg.error.extract_sign",
- }
- }
-
- keyID := ""
- if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
- keyID = fmt.Sprintf("%X", *sig.IssuerKeyId)
- }
- if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
- keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
- }
- defaultReason := NoKeyFound
-
- // First check if the sig has a keyID and if so just look at that
- if commitVerification := hashAndVerifyForKeyID(
- ctx,
- sig,
- c.Signature.Payload,
- committer,
- keyID,
- setting.AppName,
- ""); commitVerification != nil {
- if commitVerification.Reason == BadSignature {
- defaultReason = BadSignature
- } else {
- return commitVerification
- }
- }
-
- // Now try to associate the signature with the committer, if present
- if committer.ID != 0 {
- keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
- OwnerID: committer.ID,
- })
- if err != nil { // Skipping failed to get gpg keys of user
- log.Error("ListGPGKeys: %v", err)
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false,
- Reason: "gpg.error.failed_retrieval_gpg_keys",
- }
- }
-
- if err := GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
- log.Error("LoadSubKeys: %v", err)
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false,
- Reason: "gpg.error.failed_retrieval_gpg_keys",
- }
- }
-
- committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID)
- activated := false
- for _, e := range committerEmailAddresses {
- if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
- activated = true
- break
- }
- }
-
- for _, k := range keys {
- // Pre-check (& optimization) that emails attached to key can be attached to the committer email and can validate
- canValidate := false
- email := ""
- if k.Verified && activated {
- canValidate = true
- email = c.Committer.Email
- }
- if !canValidate {
- for _, e := range k.Emails {
- if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
- canValidate = true
- email = e.Email
- break
- }
- }
- }
- if !canValidate {
- continue // Skip this key
- }
-
- commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, c.Signature.Payload, k, committer, committer, email)
- if commitVerification != nil {
- return commitVerification
- }
- }
- }
-
- if setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
- // OK we should try the default key
- gpgSettings := git.GPGSettings{
- Sign: true,
- KeyID: setting.Repository.Signing.SigningKey,
- Name: setting.Repository.Signing.SigningName,
- Email: setting.Repository.Signing.SigningEmail,
- }
- if err := gpgSettings.LoadPublicKeyContent(); err != nil {
- log.Error("Error getting default signing key: %s %v", gpgSettings.KeyID, err)
- } else if commitVerification := verifyWithGPGSettings(ctx, &gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
- if commitVerification.Reason == BadSignature {
- defaultReason = BadSignature
- } else {
- return commitVerification
- }
- }
- }
-
- defaultGPGSettings, err := c.GetRepositoryDefaultPublicGPGKey(false)
- if err != nil {
- log.Error("Error getting default public gpg key: %v", err)
- } else if defaultGPGSettings == nil {
- log.Warn("Unable to get defaultGPGSettings for unattached commit: %s", c.ID.String())
- } else if defaultGPGSettings.Sign {
- if commitVerification := verifyWithGPGSettings(ctx, defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
- if commitVerification.Reason == BadSignature {
- defaultReason = BadSignature
- } else {
- return commitVerification
- }
- }
- }
-
- return &CommitVerification{ // Default at this stage
- CommittingUser: committer,
- Verified: false,
- Warning: defaultReason != NoKeyFound,
- Reason: defaultReason,
- SigningKey: &GPGKey{
- KeyID: keyID,
- },
- }
-}
-
-func verifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *CommitVerification {
- // First try to find the key in the db
- if commitVerification := hashAndVerifyForKeyID(ctx, sig, payload, committer, gpgSettings.KeyID, gpgSettings.Name, gpgSettings.Email); commitVerification != nil {
- return commitVerification
- }
-
- // Otherwise we have to parse the key
- ekeys, err := checkArmoredGPGKeyString(gpgSettings.PublicKeyContent)
- if err != nil {
- log.Error("Unable to get default signing key: %v", err)
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false,
- Reason: "gpg.error.generate_hash",
- }
- }
- for _, ekey := range ekeys {
- pubkey := ekey.PrimaryKey
- content, err := base64EncPubKey(pubkey)
- if err != nil {
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false,
- Reason: "gpg.error.generate_hash",
- }
- }
- k := &GPGKey{
- Content: content,
- CanSign: pubkey.CanSign(),
- KeyID: pubkey.KeyIdString(),
- }
- for _, subKey := range ekey.Subkeys {
- content, err := base64EncPubKey(subKey.PublicKey)
- if err != nil {
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false,
- Reason: "gpg.error.generate_hash",
- }
- }
- k.SubsKey = append(k.SubsKey, &GPGKey{
- Content: content,
- CanSign: subKey.PublicKey.CanSign(),
- KeyID: subKey.PublicKey.KeyIdString(),
- })
- }
- if commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, payload, k, committer, &user_model.User{
- Name: gpgSettings.Name,
- Email: gpgSettings.Email,
- }, gpgSettings.Email); commitVerification != nil {
- return commitVerification
- }
- if keyID == k.KeyID {
- // This is a bad situation ... We have a key id that matches our default key but the signature doesn't match.
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false,
- Warning: true,
- Reason: BadSignature,
- }
- }
- }
- return nil
-}
-
-func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error {
- // Check if key can sign
- if !k.CanSign {
- return fmt.Errorf("key can not sign")
- }
- // Decode key
- pkey, err := base64DecPubKey(k.Content)
- if err != nil {
- return err
- }
- return pkey.VerifySignature(h, s)
-}
-
-func hashAndVerify(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
- // Generating hash of commit
- hash, err := populateHash(sig.Hash, []byte(payload))
- if err != nil { // Skipping as failed to generate hash
- log.Error("PopulateHash: %v", err)
- return nil, err
- }
- // We will ignore errors in verification as they don't need to be propagated up
- err = verifySign(sig, hash, k)
- if err != nil {
- return nil, nil
- }
- return k, nil
-}
-
-func hashAndVerifyWithSubKeys(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
- verified, err := hashAndVerify(sig, payload, k)
- if err != nil || verified != nil {
- return verified, err
- }
- for _, sk := range k.SubsKey {
- verified, err := hashAndVerify(sig, payload, sk)
- if err != nil || verified != nil {
- return verified, err
- }
- }
- return nil, nil
-}
-
-func hashAndVerifyWithSubKeysCommitVerification(sig *packet.Signature, payload string, k *GPGKey, committer, signer *user_model.User, email string) *CommitVerification {
- key, err := hashAndVerifyWithSubKeys(sig, payload, k)
- if err != nil { // Skipping failed to generate hash
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false,
- Reason: "gpg.error.generate_hash",
- }
- }
-
- if key != nil {
- return &CommitVerification{ // Everything is ok
- CommittingUser: committer,
- Verified: true,
- Reason: fmt.Sprintf("%s / %s", signer.Name, key.KeyID),
- SigningUser: signer,
- SigningKey: key,
- SigningEmail: email,
- }
- }
- return nil
-}
-
-func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload string, committer *user_model.User, keyID, name, email string) *CommitVerification {
- if keyID == "" {
- return nil
- }
- keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
- KeyID: keyID,
- IncludeSubKeys: true,
- })
- if err != nil {
- log.Error("GetGPGKeysByKeyID: %v", err)
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false,
- Reason: "gpg.error.failed_retrieval_gpg_keys",
- }
- }
- if len(keys) == 0 {
- return nil
- }
- for _, key := range keys {
- var primaryKeys []*GPGKey
- if key.PrimaryKeyID != "" {
- primaryKeys, err = db.Find[GPGKey](ctx, FindGPGKeyOptions{
- KeyID: key.PrimaryKeyID,
- IncludeSubKeys: true,
- })
- if err != nil {
- log.Error("GetGPGKeysByKeyID: %v", err)
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false,
- Reason: "gpg.error.failed_retrieval_gpg_keys",
- }
- }
- }
-
- activated, email := checkKeyEmails(ctx, email, append([]*GPGKey{key}, primaryKeys...)...)
- if !activated {
- continue
- }
-
- signer := &user_model.User{
- Name: name,
- Email: email,
- }
- if key.OwnerID != 0 {
- owner, err := user_model.GetUserByID(ctx, key.OwnerID)
- if err == nil {
- signer = owner
- } else if !user_model.IsErrUserNotExist(err) {
- log.Error("Failed to user_model.GetUserByID: %d for key ID: %d (%s) %v", key.OwnerID, key.ID, key.KeyID, err)
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false,
- Reason: "gpg.error.no_committer_account",
- }
- }
- }
- commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, payload, key, committer, signer, email)
- if commitVerification != nil {
- return commitVerification
- }
- }
- // This is a bad situation ... We have a key id that is in our database but the signature doesn't match.
- return &CommitVerification{
- CommittingUser: committer,
- Verified: false,
- Warning: true,
- Reason: BadSignature,
- }
-}
-
-// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
-// There are several trust models in Gitea
-func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) error {
- if !verification.Verified {
- return nil
- }
-
- // In the Committer trust model a signature is trusted if it matches the committer
- // - it doesn't matter if they're a collaborator, the owner, Gitea or Github
- // NB: This model is commit verification only
- if repoTrustModel == repo_model.CommitterTrustModel {
- // default to "unmatched"
- verification.TrustStatus = "unmatched"
-
- // We can only verify against users in our database but the default key will match
- // against by email if it is not in the db.
- if (verification.SigningUser.ID != 0 &&
- verification.CommittingUser.ID == verification.SigningUser.ID) ||
- (verification.SigningUser.ID == 0 && verification.CommittingUser.ID == 0 &&
- verification.SigningUser.Email == verification.CommittingUser.Email) {
- verification.TrustStatus = "trusted"
- }
- return nil
- }
-
- // Now we drop to the more nuanced trust models...
- verification.TrustStatus = "trusted"
-
- if verification.SigningUser.ID == 0 {
- // This commit is signed by the default key - but this key is not assigned to a user in the DB.
-
- // However in the repo_model.CollaboratorCommitterTrustModel we cannot mark this as trusted
- // unless the default key matches the email of a non-user.
- if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && (verification.CommittingUser.ID != 0 ||
- verification.SigningUser.Email != verification.CommittingUser.Email) {
- verification.TrustStatus = "untrusted"
- }
- return nil
- }
-
- // Check we actually have a GPG SigningKey
- var err error
- if verification.SigningKey != nil {
- var isMember bool
- if keyMap != nil {
- var has bool
- isMember, has = (*keyMap)[verification.SigningKey.KeyID]
- if !has {
- isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
- (*keyMap)[verification.SigningKey.KeyID] = isMember
- }
- } else {
- isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
- }
-
- if !isMember {
- verification.TrustStatus = "untrusted"
- if verification.CommittingUser.ID != verification.SigningUser.ID {
- // The committing user and the signing user are not the same
- // This should be marked as questionable unless the signing user is a collaborator/team member etc.
- verification.TrustStatus = "unmatched"
- }
- } else if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && verification.CommittingUser.ID != verification.SigningUser.ID {
- // The committing user and the signing user are not the same and our trustmodel states that they must match
- verification.TrustStatus = "unmatched"
- }
- }
-
- return err
+func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *ObjectVerification {
+ o := commitToGitObject(c)
+ return ParseObjectWithSignature(ctx, &o)
}
diff --git a/models/asymkey/gpg_key_object_verification.go b/models/asymkey/gpg_key_object_verification.go
new file mode 100644
index 0000000000..cbae280c6a
--- /dev/null
+++ b/models/asymkey/gpg_key_object_verification.go
@@ -0,0 +1,527 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package asymkey
+
+import (
+ "context"
+ "fmt"
+ "hash"
+ "strings"
+
+ "code.gitea.io/gitea/models/db"
+ repo_model "code.gitea.io/gitea/models/repo"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/keybase/go-crypto/openpgp/packet"
+)
+
+// This file provides functions related to object (commit, tag) verification
+
+// ObjectVerification represents a commit validation of signature
+type ObjectVerification struct {
+ Verified bool
+ Warning bool
+ Reason string
+ SigningUser *user_model.User
+ CommittingUser *user_model.User
+ SigningEmail string
+ SigningKey *GPGKey
+ SigningSSHKey *PublicKey
+ TrustStatus string
+}
+
+const (
+ // BadSignature is used as the reason when the signature has a KeyID that is in the db
+ // but no key that has that ID verifies the signature. This is a suspicious failure.
+ BadSignature = "gpg.error.probable_bad_signature"
+ // BadDefaultSignature is used as the reason when the signature has a KeyID that matches the
+ // default Key but is not verified by the default key. This is a suspicious failure.
+ BadDefaultSignature = "gpg.error.probable_bad_default_signature"
+ // NoKeyFound is used as the reason when no key can be found to verify the signature.
+ NoKeyFound = "gpg.error.no_gpg_keys_found"
+)
+
+type GitObject struct {
+ ID git.ObjectID
+ Committer *git.Signature
+ Signature *git.ObjectSignature
+ Commit *git.Commit
+}
+
+func commitToGitObject(c *git.Commit) GitObject {
+ return GitObject{
+ ID: c.ID,
+ Committer: c.Committer,
+ Signature: c.Signature,
+ Commit: c,
+ }
+}
+
+func tagToGitObject(t *git.Tag, gitRepo *git.Repository) GitObject {
+ commit, _ := t.Commit(gitRepo)
+ return GitObject{
+ ID: t.ID,
+ Committer: t.Tagger,
+ Signature: t.Signature,
+ Commit: commit,
+ }
+}
+
+// ParseObjectWithSignature check if signature is good against keystore.
+func ParseObjectWithSignature(ctx context.Context, c *GitObject) *ObjectVerification {
+ var committer *user_model.User
+ if c.Committer != nil {
+ var err error
+ // Find Committer account
+ committer, err = user_model.GetUserByEmail(ctx, c.Committer.Email) // This finds the user by primary email or activated email so commit will not be valid if email is not
+ if err != nil { // Skipping not user for committer
+ committer = &user_model.User{
+ Name: c.Committer.Name,
+ Email: c.Committer.Email,
+ }
+ // We can expect this to often be an ErrUserNotExist. in the case
+ // it is not, however, it is important to log it.
+ if !user_model.IsErrUserNotExist(err) {
+ log.Error("GetUserByEmail: %v", err)
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false,
+ Reason: "gpg.error.no_committer_account",
+ }
+ }
+
+ }
+ }
+
+ // If no signature just report the committer
+ if c.Signature == nil {
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false, // Default value
+ Reason: "gpg.error.not_signed_commit", // Default value
+ }
+ }
+
+ // If this a SSH signature handle it differently
+ if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") {
+ return ParseObjectWithSSHSignature(ctx, c, committer)
+ }
+
+ // Parsing signature
+ sig, err := extractSignature(c.Signature.Signature)
+ if err != nil { // Skipping failed to extract sign
+ log.Error("SignatureRead err: %v", err)
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false,
+ Reason: "gpg.error.extract_sign",
+ }
+ }
+
+ keyID := ""
+ if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
+ keyID = fmt.Sprintf("%X", *sig.IssuerKeyId)
+ }
+ if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
+ keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
+ }
+ defaultReason := NoKeyFound
+
+ // First check if the sig has a keyID and if so just look at that
+ if commitVerification := hashAndVerifyForKeyID(
+ ctx,
+ sig,
+ c.Signature.Payload,
+ committer,
+ keyID,
+ setting.AppName,
+ ""); commitVerification != nil {
+ if commitVerification.Reason == BadSignature {
+ defaultReason = BadSignature
+ } else {
+ return commitVerification
+ }
+ }
+
+ // Now try to associate the signature with the committer, if present
+ if committer.ID != 0 {
+ keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
+ OwnerID: committer.ID,
+ })
+ if err != nil { // Skipping failed to get gpg keys of user
+ log.Error("ListGPGKeys: %v", err)
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false,
+ Reason: "gpg.error.failed_retrieval_gpg_keys",
+ }
+ }
+
+ if err := GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
+ log.Error("LoadSubKeys: %v", err)
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false,
+ Reason: "gpg.error.failed_retrieval_gpg_keys",
+ }
+ }
+
+ committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID)
+ activated := false
+ for _, e := range committerEmailAddresses {
+ if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
+ activated = true
+ break
+ }
+ }
+
+ for _, k := range keys {
+ // Pre-check (& optimization) that emails attached to key can be attached to the committer email and can validate
+ canValidate := false
+ email := ""
+ if k.Verified && activated {
+ canValidate = true
+ email = c.Committer.Email
+ }
+ if !canValidate {
+ for _, e := range k.Emails {
+ if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
+ canValidate = true
+ email = e.Email
+ break
+ }
+ }
+ }
+ if !canValidate {
+ continue // Skip this key
+ }
+
+ commitVerification := hashAndVerifyWithSubKeysObjectVerification(sig, c.Signature.Payload, k, committer, committer, email)
+ if commitVerification != nil {
+ return commitVerification
+ }
+ }
+ }
+
+ if setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
+ // OK we should try the default key
+ gpgSettings := git.GPGSettings{
+ Sign: true,
+ KeyID: setting.Repository.Signing.SigningKey,
+ Name: setting.Repository.Signing.SigningName,
+ Email: setting.Repository.Signing.SigningEmail,
+ }
+ if err := gpgSettings.LoadPublicKeyContent(); err != nil {
+ log.Error("Error getting default signing key: %s %v", gpgSettings.KeyID, err)
+ } else if commitVerification := verifyWithGPGSettings(ctx, &gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
+ if commitVerification.Reason == BadSignature {
+ defaultReason = BadSignature
+ } else {
+ return commitVerification
+ }
+ }
+ }
+
+ defaultGPGSettings, err := c.Commit.GetRepositoryDefaultPublicGPGKey(false)
+ if err != nil {
+ log.Error("Error getting default public gpg key: %v", err)
+ } else if defaultGPGSettings == nil {
+ log.Warn("Unable to get defaultGPGSettings for unattached commit: %s", c.Commit.ID.String())
+ } else if defaultGPGSettings.Sign {
+ if commitVerification := verifyWithGPGSettings(ctx, defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
+ if commitVerification.Reason == BadSignature {
+ defaultReason = BadSignature
+ } else {
+ return commitVerification
+ }
+ }
+ }
+
+ return &ObjectVerification{ // Default at this stage
+ CommittingUser: committer,
+ Verified: false,
+ Warning: defaultReason != NoKeyFound,
+ Reason: defaultReason,
+ SigningKey: &GPGKey{
+ KeyID: keyID,
+ },
+ }
+}
+
+func verifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *ObjectVerification {
+ // First try to find the key in the db
+ if commitVerification := hashAndVerifyForKeyID(ctx, sig, payload, committer, gpgSettings.KeyID, gpgSettings.Name, gpgSettings.Email); commitVerification != nil {
+ return commitVerification
+ }
+
+ // Otherwise we have to parse the key
+ ekeys, err := checkArmoredGPGKeyString(gpgSettings.PublicKeyContent)
+ if err != nil {
+ log.Error("Unable to get default signing key: %v", err)
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false,
+ Reason: "gpg.error.generate_hash",
+ }
+ }
+ for _, ekey := range ekeys {
+ pubkey := ekey.PrimaryKey
+ content, err := base64EncPubKey(pubkey)
+ if err != nil {
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false,
+ Reason: "gpg.error.generate_hash",
+ }
+ }
+ k := &GPGKey{
+ Content: content,
+ CanSign: pubkey.CanSign(),
+ KeyID: pubkey.KeyIdString(),
+ }
+ for _, subKey := range ekey.Subkeys {
+ content, err := base64EncPubKey(subKey.PublicKey)
+ if err != nil {
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false,
+ Reason: "gpg.error.generate_hash",
+ }
+ }
+ k.SubsKey = append(k.SubsKey, &GPGKey{
+ Content: content,
+ CanSign: subKey.PublicKey.CanSign(),
+ KeyID: subKey.PublicKey.KeyIdString(),
+ })
+ }
+ if commitVerification := hashAndVerifyWithSubKeysObjectVerification(sig, payload, k, committer, &user_model.User{
+ Name: gpgSettings.Name,
+ Email: gpgSettings.Email,
+ }, gpgSettings.Email); commitVerification != nil {
+ return commitVerification
+ }
+ if keyID == k.KeyID {
+ // This is a bad situation ... We have a key id that matches our default key but the signature doesn't match.
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false,
+ Warning: true,
+ Reason: BadSignature,
+ }
+ }
+ }
+ return nil
+}
+
+func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error {
+ // Check if key can sign
+ if !k.CanSign {
+ return fmt.Errorf("key can not sign")
+ }
+ // Decode key
+ pkey, err := base64DecPubKey(k.Content)
+ if err != nil {
+ return err
+ }
+ return pkey.VerifySignature(h, s)
+}
+
+func hashAndVerify(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
+ // Generating hash of commit
+ hash, err := populateHash(sig.Hash, []byte(payload))
+ if err != nil { // Skipping as failed to generate hash
+ log.Error("PopulateHash: %v", err)
+ return nil, err
+ }
+ // We will ignore errors in verification as they don't need to be propagated up
+ err = verifySign(sig, hash, k)
+ if err != nil {
+ return nil, nil
+ }
+ return k, nil
+}
+
+func hashAndVerifyWithSubKeys(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
+ verified, err := hashAndVerify(sig, payload, k)
+ if err != nil || verified != nil {
+ return verified, err
+ }
+ for _, sk := range k.SubsKey {
+ verified, err := hashAndVerify(sig, payload, sk)
+ if err != nil || verified != nil {
+ return verified, err
+ }
+ }
+ return nil, nil
+}
+
+func hashAndVerifyWithSubKeysObjectVerification(sig *packet.Signature, payload string, k *GPGKey, committer, signer *user_model.User, email string) *ObjectVerification {
+ key, err := hashAndVerifyWithSubKeys(sig, payload, k)
+ if err != nil { // Skipping failed to generate hash
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false,
+ Reason: "gpg.error.generate_hash",
+ }
+ }
+
+ if key != nil {
+ return &ObjectVerification{ // Everything is ok
+ CommittingUser: committer,
+ Verified: true,
+ Reason: fmt.Sprintf("%s / %s", signer.Name, key.KeyID),
+ SigningUser: signer,
+ SigningKey: key,
+ SigningEmail: email,
+ }
+ }
+ return nil
+}
+
+func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload string, committer *user_model.User, keyID, name, email string) *ObjectVerification {
+ if keyID == "" {
+ return nil
+ }
+ keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
+ KeyID: keyID,
+ IncludeSubKeys: true,
+ })
+ if err != nil {
+ log.Error("GetGPGKeysByKeyID: %v", err)
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false,
+ Reason: "gpg.error.failed_retrieval_gpg_keys",
+ }
+ }
+ if len(keys) == 0 {
+ return nil
+ }
+ for _, key := range keys {
+ var primaryKeys []*GPGKey
+ if key.PrimaryKeyID != "" {
+ primaryKeys, err = db.Find[GPGKey](ctx, FindGPGKeyOptions{
+ KeyID: key.PrimaryKeyID,
+ IncludeSubKeys: true,
+ })
+ if err != nil {
+ log.Error("GetGPGKeysByKeyID: %v", err)
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false,
+ Reason: "gpg.error.failed_retrieval_gpg_keys",
+ }
+ }
+ }
+
+ activated, email := checkKeyEmails(ctx, email, append([]*GPGKey{key}, primaryKeys...)...)
+ if !activated {
+ continue
+ }
+
+ signer := &user_model.User{
+ Name: name,
+ Email: email,
+ }
+ if key.OwnerID != 0 {
+ owner, err := user_model.GetUserByID(ctx, key.OwnerID)
+ if err == nil {
+ signer = owner
+ } else if !user_model.IsErrUserNotExist(err) {
+ log.Error("Failed to user_model.GetUserByID: %d for key ID: %d (%s) %v", key.OwnerID, key.ID, key.KeyID, err)
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false,
+ Reason: "gpg.error.no_committer_account",
+ }
+ }
+ }
+ commitVerification := hashAndVerifyWithSubKeysObjectVerification(sig, payload, key, committer, signer, email)
+ if commitVerification != nil {
+ return commitVerification
+ }
+ }
+ // This is a bad situation ... We have a key id that is in our database but the signature doesn't match.
+ return &ObjectVerification{
+ CommittingUser: committer,
+ Verified: false,
+ Warning: true,
+ Reason: BadSignature,
+ }
+}
+
+// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
+// There are several trust models in Gitea
+func CalculateTrustStatus(verification *ObjectVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) error {
+ if !verification.Verified {
+ return nil
+ }
+
+ // In the Committer trust model a signature is trusted if it matches the committer
+ // - it doesn't matter if they're a collaborator, the owner, Gitea or Github
+ // NB: This model is commit verification only
+ if repoTrustModel == repo_model.CommitterTrustModel {
+ // default to "unmatched"
+ verification.TrustStatus = "unmatched"
+
+ // We can only verify against users in our database but the default key will match
+ // against by email if it is not in the db.
+ if (verification.SigningUser.ID != 0 &&
+ verification.CommittingUser.ID == verification.SigningUser.ID) ||
+ (verification.SigningUser.ID == 0 && verification.CommittingUser.ID == 0 &&
+ verification.SigningUser.Email == verification.CommittingUser.Email) {
+ verification.TrustStatus = "trusted"
+ }
+ return nil
+ }
+
+ // Now we drop to the more nuanced trust models...
+ verification.TrustStatus = "trusted"
+
+ if verification.SigningUser.ID == 0 {
+ // This commit is signed by the default key - but this key is not assigned to a user in the DB.
+
+ // However in the repo_model.CollaboratorCommitterTrustModel we cannot mark this as trusted
+ // unless the default key matches the email of a non-user.
+ if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && (verification.CommittingUser.ID != 0 ||
+ verification.SigningUser.Email != verification.CommittingUser.Email) {
+ verification.TrustStatus = "untrusted"
+ }
+ return nil
+ }
+
+ // Check we actually have a GPG SigningKey
+ var err error
+ if verification.SigningKey != nil {
+ var isMember bool
+ if keyMap != nil {
+ var has bool
+ isMember, has = (*keyMap)[verification.SigningKey.KeyID]
+ if !has {
+ isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
+ (*keyMap)[verification.SigningKey.KeyID] = isMember
+ }
+ } else {
+ isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
+ }
+
+ if !isMember {
+ verification.TrustStatus = "untrusted"
+ if verification.CommittingUser.ID != verification.SigningUser.ID {
+ // The committing user and the signing user are not the same
+ // This should be marked as questionable unless the signing user is a collaborator/team member etc.
+ verification.TrustStatus = "unmatched"
+ }
+ } else if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && verification.CommittingUser.ID != verification.SigningUser.ID {
+ // The committing user and the signing user are not the same and our trustmodel states that they must match
+ verification.TrustStatus = "unmatched"
+ }
+ }
+
+ return err
+}
diff --git a/models/asymkey/gpg_key_tag_verification.go b/models/asymkey/gpg_key_tag_verification.go
new file mode 100644
index 0000000000..5fd3983e54
--- /dev/null
+++ b/models/asymkey/gpg_key_tag_verification.go
@@ -0,0 +1,15 @@
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package asymkey
+
+import (
+ "context"
+
+ "code.gitea.io/gitea/modules/git"
+)
+
+func ParseTagWithSignature(ctx context.Context, gitRepo *git.Repository, t *git.Tag) *ObjectVerification {
+ o := tagToGitObject(t, gitRepo)
+ return ParseObjectWithSignature(ctx, &o)
+}
diff --git a/models/asymkey/ssh_key_commit_verification.go b/models/asymkey/ssh_key_object_verification.go
index 2b802710a8..5ad6fdb0a9 100644
--- a/models/asymkey/ssh_key_commit_verification.go
+++ b/models/asymkey/ssh_key_object_verification.go
@@ -11,14 +11,13 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"github.com/42wim/sshsig"
)
-// ParseCommitWithSSHSignature check if signature is good against keystore.
-func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *CommitVerification {
+// ParseObjectWithSSHSignature check if signature is good against keystore.
+func ParseObjectWithSSHSignature(ctx context.Context, c *GitObject, committer *user_model.User) *ObjectVerification {
// Now try to associate the signature with the committer, if present
if committer.ID != 0 {
keys, err := db.Find[PublicKey](ctx, FindPublicKeyOptions{
@@ -27,7 +26,7 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
})
if err != nil { // Skipping failed to get ssh keys of user
log.Error("ListPublicKeys: %v", err)
- return &CommitVerification{
+ return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
@@ -55,7 +54,7 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
for _, k := range keys {
if k.Verified && activated {
- commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email)
+ commitVerification := verifySSHObjectVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email)
if commitVerification != nil {
return commitVerification
}
@@ -63,19 +62,19 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
}
}
- return &CommitVerification{
+ return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: NoKeyFound,
}
}
-func verifySSHCommitVerification(sig, payload string, k *PublicKey, committer, signer *user_model.User, email string) *CommitVerification {
+func verifySSHObjectVerification(sig, payload string, k *PublicKey, committer, signer *user_model.User, email string) *ObjectVerification {
if err := sshsig.Verify(bytes.NewBuffer([]byte(payload)), []byte(sig), []byte(k.Content), "git"); err != nil {
return nil
}
- return &CommitVerification{ // Everything is ok
+ return &ObjectVerification{ // Everything is ok
CommittingUser: committer,
Verified: true,
Reason: fmt.Sprintf("%s / %s", signer.Name, k.Fingerprint),
diff --git a/models/asymkey/ssh_key_commit_verification_test.go b/models/asymkey/ssh_key_object_verification_test.go
index 320e114b3d..4e229c9b13 100644
--- a/models/asymkey/ssh_key_commit_verification_test.go
+++ b/models/asymkey/ssh_key_object_verification_test.go
@@ -22,7 +22,8 @@ func TestParseCommitWithSSHSignature(t *testing.T) {
sshKey := unittest.AssertExistsAndLoadBean(t, &PublicKey{ID: 1000, OwnerID: 2})
t.Run("No commiter", func(t *testing.T) {
- commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, &git.Commit{}, &user_model.User{})
+ o := commitToGitObject(&git.Commit{})
+ commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, &user_model.User{})
assert.False(t, commitVerification.Verified)
assert.Equal(t, NoKeyFound, commitVerification.Reason)
})
@@ -30,7 +31,8 @@ func TestParseCommitWithSSHSignature(t *testing.T) {
t.Run("Commiter without keys", func(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
- commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, &git.Commit{Committer: &git.Signature{Email: user.Email}}, user)
+ o := commitToGitObject(&git.Commit{Committer: &git.Signature{Email: user.Email}})
+ commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user)
assert.False(t, commitVerification.Verified)
assert.Equal(t, NoKeyFound, commitVerification.Reason)
})
@@ -57,7 +59,8 @@ AAAAQIMufOuSjZeDUujrkVK4sl7ICa0WwEftas8UAYxx0Thdkiw2qWjR1U1PKfTLm16/w8
`,
},
}
- commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
+ o := commitToGitObject(gitCommit)
+ commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
assert.False(t, commitVerification.Verified)
assert.Equal(t, NoKeyFound, commitVerification.Reason)
})
@@ -79,7 +82,8 @@ Add content
},
}
- commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
+ o := commitToGitObject(gitCommit)
+ commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
assert.False(t, commitVerification.Verified)
assert.Equal(t, NoKeyFound, commitVerification.Reason)
})
@@ -107,7 +111,8 @@ fs9cMpZVM9BfIKNUSO8QY=
},
}
- commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
+ o := commitToGitObject(gitCommit)
+ commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
assert.True(t, commitVerification.Verified)
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
@@ -138,7 +143,8 @@ muPLbvEduU+Ze/1Ol1pgk=
},
}
- commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
+ o := commitToGitObject(gitCommit)
+ commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
assert.True(t, commitVerification.Verified)
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
diff --git a/models/db/context.go b/models/db/context.go
index cda608af19..43f612518a 100644
--- a/models/db/context.go
+++ b/models/db/context.go
@@ -120,6 +120,16 @@ func (c *halfCommitter) Close() error {
// TxContext represents a transaction Context,
// it will reuse the existing transaction in the parent context or create a new one.
+// Some tips to use:
+//
+// 1 It's always recommended to use `WithTx` in new code instead of `TxContext`, since `WithTx` will handle the transaction automatically.
+// 2. To maintain the old code which uses `TxContext`:
+// a. Always call `Close()` before returning regardless of whether `Commit()` has been called.
+// b. Always call `Commit()` before returning if there are no errors, even if the code did not change any data.
+// c. Remember the `Committer` will be a halfCommitter when a transaction is being reused.
+// So calling `Commit()` will do nothing, but calling `Close()` without calling `Commit()` will rollback the transaction.
+// And all operations submitted by the caller stack will be rollbacked as well, not only the operations in the current function.
+// d. It doesn't mean rollback is forbidden, but always do it only when there is an error, and you do want to rollback.
func TxContext(parentCtx context.Context) (*Context, Committer, error) {
if sess, ok := inTransaction(parentCtx); ok {
return newContext(parentCtx, sess, true), &halfCommitter{committer: sess}, nil
diff --git a/models/fixtures/comment.yml b/models/fixtures/comment.yml
index 17586caa21..74fc716180 100644
--- a/models/fixtures/comment.yml
+++ b/models/fixtures/comment.yml
@@ -75,3 +75,11 @@
content: "comment in private pository"
created_unix: 946684811
updated_unix: 946684811
+
+-
+ id: 9
+ type: 22 # review
+ poster_id: 2
+ issue_id: 2 # in repo_id 1
+ review_id: 20
+ created_unix: 946684810
diff --git a/models/fixtures/project.yml b/models/fixtures/project.yml
index 1bf8030f6a..44d87bce04 100644
--- a/models/fixtures/project.yml
+++ b/models/fixtures/project.yml
@@ -45,3 +45,27 @@
type: 2
created_unix: 1688973000
updated_unix: 1688973000
+
+-
+ id: 5
+ title: project without default column
+ owner_id: 2
+ repo_id: 0
+ is_closed: false
+ creator_id: 2
+ board_type: 1
+ type: 2
+ created_unix: 1688973000
+ updated_unix: 1688973000
+
+-
+ id: 6
+ title: project with multiple default columns
+ owner_id: 2
+ repo_id: 0
+ is_closed: false
+ creator_id: 2
+ board_type: 1
+ type: 2
+ created_unix: 1688973000
+ updated_unix: 1688973000
diff --git a/models/fixtures/project_board.yml b/models/fixtures/project_board.yml
index dc4f9cf565..3293dea6ed 100644
--- a/models/fixtures/project_board.yml
+++ b/models/fixtures/project_board.yml
@@ -3,6 +3,7 @@
project_id: 1
title: To Do
creator_id: 2
+ default: true
created_unix: 1588117528
updated_unix: 1588117528
@@ -29,3 +30,48 @@
creator_id: 2
created_unix: 1588117528
updated_unix: 1588117528
+
+-
+ id: 5
+ project_id: 2
+ title: Backlog
+ creator_id: 2
+ default: true
+ created_unix: 1588117528
+ updated_unix: 1588117528
+
+-
+ id: 6
+ project_id: 4
+ title: Backlog
+ creator_id: 2
+ default: true
+ created_unix: 1588117528
+ updated_unix: 1588117528
+
+-
+ id: 7
+ project_id: 5
+ title: Done
+ creator_id: 2
+ default: false
+ created_unix: 1588117528
+ updated_unix: 1588117528
+
+-
+ id: 8
+ project_id: 6
+ title: Backlog
+ creator_id: 2
+ default: true
+ created_unix: 1588117528
+ updated_unix: 1588117528
+
+-
+ id: 9
+ project_id: 6
+ title: Uncategorized
+ creator_id: 2
+ default: true
+ created_unix: 1588117528
+ updated_unix: 1588117528
diff --git a/models/fixtures/review.yml b/models/fixtures/review.yml
index 7a88080068..ac97e24c2b 100644
--- a/models/fixtures/review.yml
+++ b/models/fixtures/review.yml
@@ -170,3 +170,12 @@
content: "review request for user15"
updated_unix: 946684835
created_unix: 946684835
+
+-
+ id: 20
+ type: 22
+ reviewer_id: 1
+ issue_id: 2
+ content: "Review Comment"
+ updated_unix: 946684810
+ created_unix: 946684810
diff --git a/models/git/commit_status.go b/models/git/commit_status.go
index d204d11c28..e44cc98985 100644
--- a/models/git/commit_status.go
+++ b/models/git/commit_status.go
@@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/translation"
"xorm.io/builder"
+ "xorm.io/xorm"
)
// CommitStatus holds a single Status of a single Commit
@@ -264,44 +265,48 @@ type CommitStatusIndex struct {
// GetLatestCommitStatus returns all statuses with a unique context for a given commit.
func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, int64, error) {
- ids := make([]int64, 0, 10)
- sess := db.GetEngine(ctx).Table(&CommitStatus{}).
- Where("repo_id = ?", repoID).And("sha = ?", sha).
- Select("max( id ) as id").
- GroupBy("context_hash").OrderBy("max( id ) desc")
+ getBase := func() *xorm.Session {
+ return db.GetEngine(ctx).Table(&CommitStatus{}).
+ Where("repo_id = ?", repoID).And("sha = ?", sha)
+ }
+ indices := make([]int64, 0, 10)
+ sess := getBase().Select("max( `index` ) as `index`").
+ GroupBy("context_hash").OrderBy("max( `index` ) desc")
if !listOptions.IsListAll() {
sess = db.SetSessionPagination(sess, &listOptions)
}
- count, err := sess.FindAndCount(&ids)
+ count, err := sess.FindAndCount(&indices)
if err != nil {
return nil, count, err
}
- statuses := make([]*CommitStatus, 0, len(ids))
- if len(ids) == 0 {
+ statuses := make([]*CommitStatus, 0, len(indices))
+ if len(indices) == 0 {
return statuses, count, nil
}
- return statuses, count, db.GetEngine(ctx).In("id", ids).Find(&statuses)
+ return statuses, count, getBase().And(builder.In("`index`", indices)).Find(&statuses)
}
// GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs
func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHAs map[int64]string, listOptions db.ListOptions) (map[int64][]*CommitStatus, error) {
type result struct {
- ID int64
+ Index int64
RepoID int64
}
results := make([]result, 0, len(repoIDsToLatestCommitSHAs))
- sess := db.GetEngine(ctx).Table(&CommitStatus{})
+ getBase := func() *xorm.Session {
+ return db.GetEngine(ctx).Table(&CommitStatus{})
+ }
// Create a disjunction of conditions for each repoID and SHA pair
conds := make([]builder.Cond, 0, len(repoIDsToLatestCommitSHAs))
for repoID, sha := range repoIDsToLatestCommitSHAs {
conds = append(conds, builder.Eq{"repo_id": repoID, "sha": sha})
}
- sess = sess.Where(builder.Or(conds...)).
- Select("max( id ) as id, repo_id").
- GroupBy("context_hash, repo_id").OrderBy("max( id ) desc")
+ sess := getBase().Where(builder.Or(conds...)).
+ Select("max( `index` ) as `index`, repo_id").
+ GroupBy("context_hash, repo_id").OrderBy("max( `index` ) desc")
if !listOptions.IsListAll() {
sess = db.SetSessionPagination(sess, &listOptions)
@@ -312,15 +317,21 @@ func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHA
return nil, err
}
- ids := make([]int64, 0, len(results))
repoStatuses := make(map[int64][]*CommitStatus)
- for _, result := range results {
- ids = append(ids, result.ID)
- }
- statuses := make([]*CommitStatus, 0, len(ids))
- if len(ids) > 0 {
- err = db.GetEngine(ctx).In("id", ids).Find(&statuses)
+ if len(results) > 0 {
+ statuses := make([]*CommitStatus, 0, len(results))
+
+ conds = make([]builder.Cond, 0, len(results))
+ for _, result := range results {
+ cond := builder.Eq{
+ "`index`": result.Index,
+ "repo_id": result.RepoID,
+ "sha": repoIDsToLatestCommitSHAs[result.RepoID],
+ }
+ conds = append(conds, cond)
+ }
+ err = getBase().Where(builder.Or(conds...)).Find(&statuses)
if err != nil {
return nil, err
}
@@ -337,42 +348,43 @@ func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHA
// GetLatestCommitStatusForRepoCommitIDs returns all statuses with a unique context for a given list of repo-sha pairs
func GetLatestCommitStatusForRepoCommitIDs(ctx context.Context, repoID int64, commitIDs []string) (map[string][]*CommitStatus, error) {
type result struct {
- ID int64
- Sha string
+ Index int64
+ SHA string
}
+ getBase := func() *xorm.Session {
+ return db.GetEngine(ctx).Table(&CommitStatus{}).Where("repo_id = ?", repoID)
+ }
results := make([]result, 0, len(commitIDs))
- sess := db.GetEngine(ctx).Table(&CommitStatus{})
-
- // Create a disjunction of conditions for each repoID and SHA pair
conds := make([]builder.Cond, 0, len(commitIDs))
for _, sha := range commitIDs {
conds = append(conds, builder.Eq{"sha": sha})
}
- sess = sess.Where(builder.Eq{"repo_id": repoID}.And(builder.Or(conds...))).
- Select("max( id ) as id, sha").
- GroupBy("context_hash, sha").OrderBy("max( id ) desc")
+ sess := getBase().And(builder.Or(conds...)).
+ Select("max( `index` ) as `index`, sha").
+ GroupBy("context_hash, sha").OrderBy("max( `index` ) desc")
err := sess.Find(&results)
if err != nil {
return nil, err
}
- ids := make([]int64, 0, len(results))
repoStatuses := make(map[string][]*CommitStatus)
- for _, result := range results {
- ids = append(ids, result.ID)
- }
- statuses := make([]*CommitStatus, 0, len(ids))
- if len(ids) > 0 {
- err = db.GetEngine(ctx).In("id", ids).Find(&statuses)
+ if len(results) > 0 {
+ statuses := make([]*CommitStatus, 0, len(results))
+
+ conds = make([]builder.Cond, 0, len(results))
+ for _, result := range results {
+ conds = append(conds, builder.Eq{"`index`": result.Index, "sha": result.SHA})
+ }
+ err = getBase().And(builder.Or(conds...)).Find(&statuses)
if err != nil {
return nil, err
}
- // Group the statuses by repo ID
+ // Group the statuses by commit
for _, status := range statuses {
repoStatuses[status.SHA] = append(repoStatuses[status.SHA], status)
}
@@ -383,22 +395,36 @@ func GetLatestCommitStatusForRepoCommitIDs(ctx context.Context, repoID int64, co
// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) {
+ type result struct {
+ Index int64
+ SHA string
+ }
+ getBase := func() *xorm.Session {
+ return db.GetEngine(ctx).Table(&CommitStatus{}).Where("repo_id = ?", repoID)
+ }
+
start := timeutil.TimeStampNow().AddDuration(-before)
- ids := make([]int64, 0, 10)
- if err := db.GetEngine(ctx).Table("commit_status").
- Where("repo_id = ?", repoID).
- And("updated_unix >= ?", start).
- Select("max( id ) as id").
- GroupBy("context_hash").OrderBy("max( id ) desc").
- Find(&ids); err != nil {
+ results := make([]result, 0, 10)
+
+ sess := getBase().And("updated_unix >= ?", start).
+ Select("max( `index` ) as `index`, sha").
+ GroupBy("context_hash, sha").OrderBy("max( `index` ) desc")
+
+ err := sess.Find(&results)
+ if err != nil {
return nil, err
}
- contexts := make([]string, 0, len(ids))
- if len(ids) == 0 {
+ contexts := make([]string, 0, len(results))
+ if len(results) == 0 {
return contexts, nil
}
- return contexts, db.GetEngine(ctx).Select("context").Table("commit_status").In("id", ids).Find(&contexts)
+
+ conds := make([]builder.Cond, 0, len(results))
+ for _, result := range results {
+ conds = append(conds, builder.Eq{"`index`": result.Index, "sha": result.SHA})
+ }
+ return contexts, getBase().And(builder.Or(conds...)).Select("context").Find(&contexts)
}
// NewCommitStatusOptions holds options for creating a CommitStatus
diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go
index cef2ea281c..735c9f0893 100644
--- a/models/issues/comment_code.go
+++ b/models/issues/comment_code.go
@@ -117,6 +117,10 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
return nil, err
}
+ if err := comments.LoadAttachments(ctx); err != nil {
+ return nil, err
+ }
+
// Find all reviews by ReviewID
reviews := make(map[int64]*Review)
ids := make([]int64, 0, len(comments))
diff --git a/models/issues/issue_project.go b/models/issues/issue_project.go
index cc7ffb356a..907a5a17b9 100644
--- a/models/issues/issue_project.go
+++ b/models/issues/issue_project.go
@@ -49,18 +49,13 @@ func (issue *Issue) ProjectBoardID(ctx context.Context) int64 {
// LoadIssuesFromBoard load issues assigned to this board
func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList, error) {
- issueList := make(IssueList, 0, 10)
-
- if b.ID > 0 {
- issues, err := Issues(ctx, &IssuesOptions{
- ProjectBoardID: b.ID,
- ProjectID: b.ProjectID,
- SortType: "project-column-sorting",
- })
- if err != nil {
- return nil, err
- }
- issueList = issues
+ issueList, err := Issues(ctx, &IssuesOptions{
+ ProjectBoardID: b.ID,
+ ProjectID: b.ProjectID,
+ SortType: "project-column-sorting",
+ })
+ if err != nil {
+ return nil, err
}
if b.Default {
diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go
index 4e1bd9e87e..921dd9973e 100644
--- a/models/issues/issue_search.go
+++ b/models/issues/issue_search.go
@@ -21,7 +21,7 @@ import (
// IssuesOptions represents options of an issue.
type IssuesOptions struct { //nolint
- db.Paginator
+ Paginator *db.ListOptions
RepoIDs []int64 // overwrites RepoCond if the length is not 0
AllPublic bool // include also all public repositories
RepoCond builder.Cond
@@ -104,23 +104,11 @@ func applyLimit(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
return sess
}
- // Warning: Do not use GetSkipTake() for *db.ListOptions
- // Its implementation could reset the page size with setting.API.MaxResponseItems
- if listOptions, ok := opts.Paginator.(*db.ListOptions); ok {
- if listOptions.Page >= 0 && listOptions.PageSize > 0 {
- var start int
- if listOptions.Page == 0 {
- start = 0
- } else {
- start = (listOptions.Page - 1) * listOptions.PageSize
- }
- sess.Limit(listOptions.PageSize, start)
- }
- return sess
+ start := 0
+ if opts.Paginator.Page > 1 {
+ start = (opts.Paginator.Page - 1) * opts.Paginator.PageSize
}
-
- start, limit := opts.Paginator.GetSkipTake()
- sess.Limit(limit, start)
+ sess.Limit(opts.Paginator.PageSize, start)
return sess
}
diff --git a/models/issues/issue_stats.go b/models/issues/issue_stats.go
index 32c5674fc9..39326616f8 100644
--- a/models/issues/issue_stats.go
+++ b/models/issues/issue_stats.go
@@ -68,13 +68,17 @@ func CountIssuesByRepo(ctx context.Context, opts *IssuesOptions) (map[int64]int6
}
// CountIssues number return of issues by given conditions.
-func CountIssues(ctx context.Context, opts *IssuesOptions) (int64, error) {
+func CountIssues(ctx context.Context, opts *IssuesOptions, otherConds ...builder.Cond) (int64, error) {
sess := db.GetEngine(ctx).
Select("COUNT(issue.id) AS count").
Table("issue").
Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
applyConditions(sess, opts)
+ for _, cond := range otherConds {
+ sess.And(cond)
+ }
+
return sess.Count()
}
diff --git a/models/issues/pull.go b/models/issues/pull.go
index f1baa9b5e5..dc1b1b956a 100644
--- a/models/issues/pull.go
+++ b/models/issues/pull.go
@@ -19,7 +19,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
@@ -884,92 +883,6 @@ func MergeBlockedByOutdatedBranch(protectBranch *git_model.ProtectedBranch, pr *
return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0
}
-func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullRequest) error {
- files := []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}
-
- if pr.IsWorkInProgress(ctx) {
- return nil
- }
-
- if err := pull.LoadRepo(ctx); err != nil {
- return err
- }
-
- if pull.Repo.IsFork {
- return nil
- }
-
- if err := pr.LoadBaseRepo(ctx); err != nil {
- return err
- }
-
- repo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
- if err != nil {
- return err
- }
- defer repo.Close()
-
- commit, err := repo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
- if err != nil {
- return err
- }
-
- var data string
- for _, file := range files {
- if blob, err := commit.GetBlobByPath(file); err == nil {
- data, err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
- if err == nil {
- break
- }
- }
- }
-
- rules, _ := GetCodeOwnersFromContent(ctx, data)
-
- prInfo, err := repo.GetCompareInfo(repo.Path, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false)
- if err != nil {
- return err
- }
- // Use the merge base as the base instead of the main branch to avoid problems
- // if the pull request is out of date with the base branch.
- changedFiles, err := repo.GetFilesChangedBetween(prInfo.MergeBase, prInfo.HeadCommitID)
- if err != nil {
- return err
- }
-
- uniqUsers := make(map[int64]*user_model.User)
- uniqTeams := make(map[string]*org_model.Team)
- for _, rule := range rules {
- for _, f := range changedFiles {
- if (rule.Rule.MatchString(f) && !rule.Negative) || (!rule.Rule.MatchString(f) && rule.Negative) {
- for _, u := range rule.Users {
- uniqUsers[u.ID] = u
- }
- for _, t := range rule.Teams {
- uniqTeams[fmt.Sprintf("%d/%d", t.OrgID, t.ID)] = t
- }
- }
- }
- }
-
- for _, u := range uniqUsers {
- if u.ID != pull.Poster.ID {
- if _, err := AddReviewRequest(ctx, pull, u, pull.Poster); err != nil {
- log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err)
- return err
- }
- }
- }
- for _, t := range uniqTeams {
- if _, err := AddTeamReviewRequest(ctx, pull, t, pull.Poster); err != nil {
- log.Warn("Failed add assignee team: %s to PR review: %s#%d, error: %s", t.Name, pr.BaseRepo.Name, pr.ID, err)
- return err
- }
- }
-
- return nil
-}
-
// GetCodeOwnersFromContent returns the code owners configuration
// Return empty slice if files missing
// Return warning messages on parsing errors
diff --git a/models/issues/review.go b/models/issues/review.go
index 70aba0f94d..92764db4d1 100644
--- a/models/issues/review.go
+++ b/models/issues/review.go
@@ -66,6 +66,23 @@ func (err ErrNotValidReviewRequest) Unwrap() error {
return util.ErrInvalidArgument
}
+// ErrReviewRequestOnClosedPR represents an error when an user tries to request a re-review on a closed or merged PR.
+type ErrReviewRequestOnClosedPR struct{}
+
+// IsErrReviewRequestOnClosedPR checks if an error is an ErrReviewRequestOnClosedPR.
+func IsErrReviewRequestOnClosedPR(err error) bool {
+ _, ok := err.(ErrReviewRequestOnClosedPR)
+ return ok
+}
+
+func (err ErrReviewRequestOnClosedPR) Error() string {
+ return "cannot request a re-review on a closed or merged PR"
+}
+
+func (err ErrReviewRequestOnClosedPR) Unwrap() error {
+ return util.ErrPermissionDenied
+}
+
// ReviewType defines the sort of feedback a review gives
type ReviewType int
@@ -618,9 +635,24 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo
return nil, err
}
- // skip it when reviewer hase been request to review
- if review != nil && review.Type == ReviewTypeRequest {
- return nil, nil
+ if review != nil {
+ // skip it when reviewer hase been request to review
+ if review.Type == ReviewTypeRequest {
+ return nil, committer.Commit() // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction.
+ }
+
+ if issue.IsClosed {
+ return nil, ErrReviewRequestOnClosedPR{}
+ }
+
+ if issue.IsPull {
+ if err := issue.LoadPullRequest(ctx); err != nil {
+ return nil, err
+ }
+ if issue.PullRequest.HasMerged {
+ return nil, ErrReviewRequestOnClosedPR{}
+ }
+ }
}
// if the reviewer is an official reviewer,
diff --git a/models/issues/review_test.go b/models/issues/review_test.go
index 1868cb1bfa..ac1b84adeb 100644
--- a/models/issues/review_test.go
+++ b/models/issues/review_test.go
@@ -288,3 +288,33 @@ func TestDeleteDismissedReview(t *testing.T) {
assert.NoError(t, issues_model.DeleteReview(db.DefaultContext, review))
unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: comment.ID})
}
+
+func TestAddReviewRequest(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
+ assert.NoError(t, pull.LoadIssue(db.DefaultContext))
+ issue := pull.Issue
+ assert.NoError(t, issue.LoadRepo(db.DefaultContext))
+ reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+ _, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
+ Issue: issue,
+ Reviewer: reviewer,
+ Type: issues_model.ReviewTypeReject,
+ })
+
+ assert.NoError(t, err)
+ pull.HasMerged = false
+ assert.NoError(t, pull.UpdateCols(db.DefaultContext, "has_merged"))
+ issue.IsClosed = true
+ _, err = issues_model.AddReviewRequest(db.DefaultContext, issue, reviewer, &user_model.User{})
+ assert.Error(t, err)
+ assert.True(t, issues_model.IsErrReviewRequestOnClosedPR(err))
+
+ pull.HasMerged = true
+ assert.NoError(t, pull.UpdateCols(db.DefaultContext, "has_merged"))
+ issue.IsClosed = false
+ _, err = issues_model.AddReviewRequest(db.DefaultContext, issue, reviewer, &user_model.User{})
+ assert.Error(t, err)
+ assert.True(t, issues_model.IsErrReviewRequestOnClosedPR(err))
+}
diff --git a/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml b/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml
new file mode 100644
index 0000000000..2450d20beb
--- /dev/null
+++ b/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml
@@ -0,0 +1,23 @@
+-
+ id: 1
+ title: project without default column
+ owner_id: 2
+ repo_id: 0
+ is_closed: false
+ creator_id: 2
+ board_type: 1
+ type: 2
+ created_unix: 1688973000
+ updated_unix: 1688973000
+
+-
+ id: 2
+ title: project with multiple default columns
+ owner_id: 2
+ repo_id: 0
+ is_closed: false
+ creator_id: 2
+ board_type: 1
+ type: 2
+ created_unix: 1688973000
+ updated_unix: 1688973000
diff --git a/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml b/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml
new file mode 100644
index 0000000000..2e1b1c7eee
--- /dev/null
+++ b/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml
@@ -0,0 +1,26 @@
+-
+ id: 1
+ project_id: 1
+ title: Done
+ creator_id: 2
+ default: false
+ created_unix: 1588117528
+ updated_unix: 1588117528
+
+-
+ id: 2
+ project_id: 2
+ title: Backlog
+ creator_id: 2
+ default: true
+ created_unix: 1588117528
+ updated_unix: 1588117528
+
+-
+ id: 3
+ project_id: 2
+ title: Uncategorized
+ creator_id: 2
+ default: true
+ created_unix: 1588117528
+ updated_unix: 1588117528
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 173d37234a..0fa46a8d0b 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -568,6 +568,12 @@ var migrations = []Migration{
NewMigration("Add default_wiki_branch to repository table", v1_22.AddDefaultWikiBranch),
// v290 -> v291
NewMigration("Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable),
+ // v291 -> v292
+ NewMigration("Add Index to attachment.comment_id", v1_22.AddCommentIDIndexofAttachment),
+ // v292 -> v293
+ NewMigration("Ensure every project has exactly one default column - No Op", noopMigration),
+ // v293 -> v294
+ NewMigration("Ensure every project has exactly one default column", v1_22.CheckProjectColumnsConsistency),
}
// GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v1_22/v291.go b/models/migrations/v1_22/v291.go
new file mode 100644
index 0000000000..0bfffe5d05
--- /dev/null
+++ b/models/migrations/v1_22/v291.go
@@ -0,0 +1,14 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import "xorm.io/xorm"
+
+func AddCommentIDIndexofAttachment(x *xorm.Engine) error {
+ type Attachment struct {
+ CommentID int64 `xorm:"INDEX"`
+ }
+
+ return x.Sync(&Attachment{})
+}
diff --git a/models/migrations/v1_22/v292.go b/models/migrations/v1_22/v292.go
new file mode 100644
index 0000000000..beca556aee
--- /dev/null
+++ b/models/migrations/v1_22/v292.go
@@ -0,0 +1,9 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+// NOTE: noop the original migration has bug which some projects will be skip, so
+// these projects will have no default board.
+// So that this migration will be skipped and go to v293.go
+// This file is a placeholder so that readers can know what happened
diff --git a/models/migrations/v1_22/v293.go b/models/migrations/v1_22/v293.go
new file mode 100644
index 0000000000..53cc719294
--- /dev/null
+++ b/models/migrations/v1_22/v293.go
@@ -0,0 +1,108 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+// CheckProjectColumnsConsistency ensures there is exactly one default board per project present
+func CheckProjectColumnsConsistency(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+
+ limit := setting.Database.IterateBufferSize
+ if limit <= 0 {
+ limit = 50
+ }
+
+ type Project struct {
+ ID int64
+ CreatorID int64
+ BoardID int64
+ }
+
+ type ProjectBoard struct {
+ ID int64 `xorm:"pk autoincr"`
+ Title string
+ Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board
+ Sorting int8 `xorm:"NOT NULL DEFAULT 0"`
+ Color string `xorm:"VARCHAR(7)"`
+
+ ProjectID int64 `xorm:"INDEX NOT NULL"`
+ CreatorID int64 `xorm:"NOT NULL"`
+
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ for {
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ // all these projects without defaults will be fixed in the same loop, so
+ // we just need to always get projects without defaults until no such project
+ var projects []*Project
+ if err := sess.Select("project.id as id, project.creator_id, project_board.id as board_id").
+ Join("LEFT", "project_board", "project_board.project_id = project.id AND project_board.`default`=?", true).
+ Where("project_board.id is NULL OR project_board.id = 0").
+ Limit(limit).
+ Find(&projects); err != nil {
+ return err
+ }
+
+ for _, p := range projects {
+ if _, err := sess.Insert(ProjectBoard{
+ ProjectID: p.ID,
+ Default: true,
+ Title: "Uncategorized",
+ CreatorID: p.CreatorID,
+ }); err != nil {
+ return err
+ }
+ }
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+
+ if len(projects) == 0 {
+ break
+ }
+ }
+ sess.Close()
+
+ return removeDuplicatedBoardDefault(x)
+}
+
+func removeDuplicatedBoardDefault(x *xorm.Engine) error {
+ type ProjectInfo struct {
+ ProjectID int64
+ DefaultNum int
+ }
+ var projects []ProjectInfo
+ if err := x.Select("project_id, count(*) AS default_num").
+ Table("project_board").
+ Where("`default` = ?", true).
+ GroupBy("project_id").
+ Having("count(*) > 1").
+ Find(&projects); err != nil {
+ return err
+ }
+
+ for _, project := range projects {
+ if _, err := x.Where("project_id=?", project.ProjectID).
+ Table("project_board").
+ Limit(project.DefaultNum - 1).
+ Update(map[string]bool{
+ "`default`": false,
+ }); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/models/migrations/v1_22/v293_test.go b/models/migrations/v1_22/v293_test.go
new file mode 100644
index 0000000000..ccc92f39a6
--- /dev/null
+++ b/models/migrations/v1_22/v293_test.go
@@ -0,0 +1,44 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/models/project"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_CheckProjectColumnsConsistency(t *testing.T) {
+ // Prepare and load the testing database
+ x, deferable := base.PrepareTestEnv(t, 0, new(project.Project), new(project.Board))
+ defer deferable()
+ if x == nil || t.Failed() {
+ return
+ }
+
+ assert.NoError(t, CheckProjectColumnsConsistency(x))
+
+ // check if default board was added
+ var defaultBoard project.Board
+ has, err := x.Where("project_id=? AND `default` = ?", 1, true).Get(&defaultBoard)
+ assert.NoError(t, err)
+ assert.True(t, has)
+ assert.Equal(t, int64(1), defaultBoard.ProjectID)
+ assert.True(t, defaultBoard.Default)
+
+ // check if multiple defaults, previous were removed and last will be kept
+ expectDefaultBoard, err := project.GetBoard(db.DefaultContext, 2)
+ assert.NoError(t, err)
+ assert.Equal(t, int64(2), expectDefaultBoard.ProjectID)
+ assert.False(t, expectDefaultBoard.Default)
+
+ expectNonDefaultBoard, err := project.GetBoard(db.DefaultContext, 3)
+ assert.NoError(t, err)
+ assert.Equal(t, int64(2), expectNonDefaultBoard.ProjectID)
+ assert.True(t, expectNonDefaultBoard.Default)
+}
diff --git a/models/project/board.go b/models/project/board.go
index c0e6529880..5f142a356c 100644
--- a/models/project/board.go
+++ b/models/project/board.go
@@ -123,6 +123,17 @@ func createBoardsForProjectsType(ctx context.Context, project *Project) error {
return nil
}
+ board := Board{
+ CreatedUnix: timeutil.TimeStampNow(),
+ CreatorID: project.CreatorID,
+ Title: "Backlog",
+ ProjectID: project.ID,
+ Default: true,
+ }
+ if err := db.Insert(ctx, board); err != nil {
+ return err
+ }
+
if len(items) == 0 {
return nil
}
@@ -176,6 +187,10 @@ func deleteBoardByID(ctx context.Context, boardID int64) error {
return err
}
+ if board.Default {
+ return fmt.Errorf("deleteBoardByID: cannot delete default board")
+ }
+
if err = board.removeIssues(ctx); err != nil {
return err
}
@@ -194,7 +209,6 @@ func deleteBoardByProjectID(ctx context.Context, projectID int64) error {
// GetBoard fetches the current board of a project
func GetBoard(ctx context.Context, boardID int64) (*Board, error) {
board := new(Board)
-
has, err := db.GetEngine(ctx).ID(boardID).Get(board)
if err != nil {
return nil, err
@@ -228,7 +242,6 @@ func UpdateBoard(ctx context.Context, board *Board) error {
}
// GetBoards fetches all boards related to a project
-// if no default board set, first board is a temporary "Uncategorized" board
func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
boards := make([]*Board, 0, 5)
@@ -244,53 +257,64 @@ func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
return append([]*Board{defaultB}, boards...), nil
}
-// getDefaultBoard return default board and create a dummy if none exist
+// getDefaultBoard return default board and ensure only one exists
func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) {
var board Board
- exist, err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, true).Get(&board)
+ has, err := db.GetEngine(ctx).
+ Where("project_id=? AND `default` = ?", p.ID, true).
+ Desc("id").Get(&board)
if err != nil {
return nil, err
}
- if exist {
+
+ if has {
return &board, nil
}
- // represents a board for issues not assigned to one
- return &Board{
+ // create a default board if none is found
+ board = Board{
ProjectID: p.ID,
- Title: "Uncategorized",
Default: true,
- }, nil
+ Title: "Uncategorized",
+ CreatorID: p.CreatorID,
+ }
+ if _, err := db.GetEngine(ctx).Insert(&board); err != nil {
+ return nil, err
+ }
+ return &board, nil
}
// SetDefaultBoard represents a board for issues not assigned to one
-// if boardID is 0 unset default
func SetDefaultBoard(ctx context.Context, projectID, boardID int64) error {
- _, err := db.GetEngine(ctx).Where(builder.Eq{
- "project_id": projectID,
- "`default`": true,
- }).Cols("`default`").Update(&Board{Default: false})
- if err != nil {
- return err
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ if _, err := GetBoard(ctx, boardID); err != nil {
+ return err
+ }
- if boardID > 0 {
- _, err = db.GetEngine(ctx).ID(boardID).Where(builder.Eq{"project_id": projectID}).
- Cols("`default`").Update(&Board{Default: true})
- }
+ if _, err := db.GetEngine(ctx).Where(builder.Eq{
+ "project_id": projectID,
+ "`default`": true,
+ }).Cols("`default`").Update(&Board{Default: false}); err != nil {
+ return err
+ }
- return err
+ _, err := db.GetEngine(ctx).ID(boardID).
+ Where(builder.Eq{"project_id": projectID}).
+ Cols("`default`").Update(&Board{Default: true})
+ return err
+ })
}
// UpdateBoardSorting update project board sorting
func UpdateBoardSorting(ctx context.Context, bs BoardList) error {
- for i := range bs {
- _, err := db.GetEngine(ctx).ID(bs[i].ID).Cols(
- "sorting",
- ).Update(bs[i])
- if err != nil {
- return err
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ for i := range bs {
+ if _, err := db.GetEngine(ctx).ID(bs[i].ID).Cols(
+ "sorting",
+ ).Update(bs[i]); err != nil {
+ return err
+ }
}
- }
- return nil
+ return nil
+ })
}
diff --git a/models/project/board_test.go b/models/project/board_test.go
new file mode 100644
index 0000000000..71ba29a589
--- /dev/null
+++ b/models/project/board_test.go
@@ -0,0 +1,44 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package project
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/unittest"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetDefaultBoard(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ projectWithoutDefault, err := GetProjectByID(db.DefaultContext, 5)
+ assert.NoError(t, err)
+
+ // check if default board was added
+ board, err := projectWithoutDefault.getDefaultBoard(db.DefaultContext)
+ assert.NoError(t, err)
+ assert.Equal(t, int64(5), board.ProjectID)
+ assert.Equal(t, "Uncategorized", board.Title)
+
+ projectWithMultipleDefaults, err := GetProjectByID(db.DefaultContext, 6)
+ assert.NoError(t, err)
+
+ // check if multiple defaults were removed
+ board, err = projectWithMultipleDefaults.getDefaultBoard(db.DefaultContext)
+ assert.NoError(t, err)
+ assert.Equal(t, int64(6), board.ProjectID)
+ assert.Equal(t, int64(9), board.ID)
+
+ // set 8 as default board
+ assert.NoError(t, SetDefaultBoard(db.DefaultContext, board.ProjectID, 8))
+
+ // then 9 will become a non-default board
+ board, err = GetBoard(db.DefaultContext, 9)
+ assert.NoError(t, err)
+ assert.Equal(t, int64(6), board.ProjectID)
+ assert.False(t, board.Default)
+}
diff --git a/models/project/project_test.go b/models/project/project_test.go
index 7a37c1faf2..8fbbdedecf 100644
--- a/models/project/project_test.go
+++ b/models/project/project_test.go
@@ -92,19 +92,19 @@ func TestProjectsSort(t *testing.T) {
}{
{
sortType: "default",
- wants: []int64{1, 3, 2, 4},
+ wants: []int64{1, 3, 2, 6, 5, 4},
},
{
sortType: "oldest",
- wants: []int64{4, 2, 3, 1},
+ wants: []int64{4, 5, 6, 2, 3, 1},
},
{
sortType: "recentupdate",
- wants: []int64{1, 3, 2, 4},
+ wants: []int64{1, 3, 2, 6, 5, 4},
},
{
sortType: "leastupdate",
- wants: []int64{4, 2, 3, 1},
+ wants: []int64{4, 5, 6, 2, 3, 1},
},
}
@@ -113,8 +113,8 @@ func TestProjectsSort(t *testing.T) {
OrderBy: GetSearchOrderByBySortType(tt.sortType),
})
assert.NoError(t, err)
- assert.EqualValues(t, int64(4), count)
- if assert.Len(t, projects, 4) {
+ assert.EqualValues(t, int64(6), count)
+ if assert.Len(t, projects, 6) {
for i := range projects {
assert.EqualValues(t, tt.wants[i], projects[i].ID)
}
diff --git a/models/user/email_address.go b/models/user/email_address.go
index 45bcc54aa3..85824fcdcb 100644
--- a/models/user/email_address.go
+++ b/models/user/email_address.go
@@ -278,14 +278,6 @@ func IsEmailUsed(ctx context.Context, email string) (bool, error) {
return db.GetEngine(ctx).Where("lower_email=?", strings.ToLower(email)).Get(&EmailAddress{})
}
-// DeleteInactiveEmailAddresses deletes inactive email addresses
-func DeleteInactiveEmailAddresses(ctx context.Context) error {
- _, err := db.GetEngine(ctx).
- Where("is_activated = ?", false).
- Delete(new(EmailAddress))
- return err
-}
-
// ActivateEmail activates the email address to given user.
func ActivateEmail(ctx context.Context, email *EmailAddress) error {
ctx, committer, err := db.TxContext(ctx)
diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go
index 7651a4434c..a0d1e9cb4f 100644
--- a/modules/git/repo_attribute.go
+++ b/modules/git/repo_attribute.go
@@ -4,18 +4,76 @@
package git
import (
+ "bufio"
"bytes"
"context"
"fmt"
+ "io"
"os"
"strings"
+ "sync/atomic"
- "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
)
var LinguistAttributes = []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language", "linguist-documentation", "linguist-detectable"}
+// newCheckAttrStdoutReader parses the nul-byte separated output of git check-attr on each call of
+// the returned function. The first reading error will stop the reading and be returned on all
+// subsequent calls.
+func newCheckAttrStdoutReader(r io.Reader, count int) func() (map[string]GitAttribute, error) {
+ scanner := bufio.NewScanner(r)
+
+ // adapted from bufio.ScanLines to split on nul-byte \x00
+ scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
+ if atEOF && len(data) == 0 {
+ return 0, nil, nil
+ }
+ if i := bytes.IndexByte(data, '\x00'); i >= 0 {
+ // We have a full nul-terminated line.
+ return i + 1, data[0:i], nil
+ }
+ // If we're at EOF, we have a final, non-terminated line. Return it.
+ if atEOF {
+ return len(data), data, nil
+ }
+ // Request more data.
+ return 0, nil, nil
+ })
+
+ var err error
+ nextText := func() string {
+ if err != nil {
+ return ""
+ }
+ if !scanner.Scan() {
+ err = scanner.Err()
+ if err == nil {
+ err = io.ErrUnexpectedEOF
+ }
+ return ""
+ }
+ return scanner.Text()
+ }
+ nextAttribute := func() (string, GitAttribute, error) {
+ nextText() // discard filename
+ key := nextText()
+ value := GitAttribute(nextText())
+ return key, value, err
+ }
+ return func() (map[string]GitAttribute, error) {
+ values := make(map[string]GitAttribute, count)
+ for range count {
+ k, v, err := nextAttribute()
+ if err != nil {
+ return values, err
+ }
+ values[k] = v
+ }
+ return values, scanner.Err()
+ }
+}
+
// GitAttribute exposes an attribute from the .gitattribute file
type GitAttribute string //nolint:revive
@@ -54,29 +112,15 @@ func (ca GitAttribute) Bool() optional.Option[bool] {
return optional.None[bool]()
}
-// GitAttributeFirst returns the first specified attribute
-//
-// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
-func (repo *Repository) GitAttributeFirst(treeish, filename string, attributes ...string) (GitAttribute, error) {
- values, err := repo.GitAttributes(treeish, filename, attributes...)
- if err != nil {
- return "", err
- }
- for _, a := range attributes {
- if values[a].IsSpecified() {
- return values[a], nil
- }
- }
- return "", nil
-}
-
+// gitCheckAttrCommand prepares the "git check-attr" command for later use as one-shot or streaming
+// instanciation.
func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string) (*Command, *RunOpts, context.CancelFunc, error) {
if len(attributes) == 0 {
return nil, nil, nil, fmt.Errorf("no provided attributes to check-attr")
}
env := os.Environ()
- var deleteTemporaryFile context.CancelFunc
+ var removeTempFiles context.CancelFunc = func() {}
// git < 2.40 cannot run check-attr on bare repo, but needs INDEX + WORK_TREE
hasIndex := treeish == ""
@@ -85,7 +129,7 @@ func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string
if err != nil {
return nil, nil, nil, err
}
- deleteTemporaryFile = cancel
+ removeTempFiles = cancel
env = append(env, "GIT_INDEX_FILE="+indexFilename, "GIT_WORK_TREE="+worktree)
@@ -94,16 +138,8 @@ func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string
// clear treeish to read from provided index/work_tree
treeish = ""
}
- ctx, cancel := context.WithCancel(repo.Ctx)
- if deleteTemporaryFile != nil {
- ctxCancel := cancel
- cancel = func() {
- ctxCancel()
- deleteTemporaryFile()
- }
- }
- cmd := NewCommand(ctx, "check-attr", "-z")
+ cmd := NewCommand(repo.Ctx, "check-attr", "-z")
if hasIndex {
cmd.AddArguments("--cached")
@@ -126,18 +162,34 @@ func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string
return cmd, &RunOpts{
Env: env,
Dir: repo.Path,
- }, cancel, nil
+ }, removeTempFiles, nil
+}
+
+// GitAttributeFirst returns the first specified attribute of the given filename.
+//
+// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
+func (repo *Repository) GitAttributeFirst(treeish, filename string, attributes ...string) (GitAttribute, error) {
+ values, err := repo.GitAttributes(treeish, filename, attributes...)
+ if err != nil {
+ return "", err
+ }
+ for _, a := range attributes {
+ if values[a].IsSpecified() {
+ return values[a], nil
+ }
+ }
+ return "", nil
}
-// GitAttributes returns gitattribute.
+// GitAttributes returns the gitattribute of the given filename.
//
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
func (repo *Repository) GitAttributes(treeish, filename string, attributes ...string) (map[string]GitAttribute, error) {
- cmd, runOpts, cancel, err := repo.gitCheckAttrCommand(treeish, attributes...)
+ cmd, runOpts, removeTempFiles, err := repo.gitCheckAttrCommand(treeish, attributes...)
if err != nil {
return nil, err
}
- defer cancel()
+ defer removeTempFiles()
stdOut := new(bytes.Buffer)
runOpts.Stdout = stdOut
@@ -151,163 +203,84 @@ func (repo *Repository) GitAttributes(treeish, filename string, attributes ...st
return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String())
}
- // FIXME: This is incorrect on versions < 1.8.5
- fields := bytes.Split(stdOut.Bytes(), []byte{'\000'})
-
- if len(fields)%3 != 1 {
- return nil, fmt.Errorf("wrong number of fields in return from check-attr")
- }
-
- values := make(map[string]GitAttribute, len(attributes))
- for ; len(fields) >= 3; fields = fields[3:] {
- // filename := string(fields[0])
- attribute := string(fields[1])
- value := string(fields[2])
- values[attribute] = GitAttribute(value)
- }
- return values, nil
-}
-
-type attributeTriple struct {
- Filename string
- Attribute string
- Value string
-}
-
-type nulSeparatedAttributeWriter struct {
- tmp []byte
- attributes chan attributeTriple
- closed chan struct{}
- working attributeTriple
- pos int
-}
-
-func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
- l, read := len(p), 0
-
- nulIdx := bytes.IndexByte(p, '\x00')
- for nulIdx >= 0 {
- wr.tmp = append(wr.tmp, p[:nulIdx]...)
- switch wr.pos {
- case 0:
- wr.working = attributeTriple{
- Filename: string(wr.tmp),
- }
- case 1:
- wr.working.Attribute = string(wr.tmp)
- case 2:
- wr.working.Value = string(wr.tmp)
- }
- wr.tmp = wr.tmp[:0]
- wr.pos++
- if wr.pos > 2 {
- wr.attributes <- wr.working
- wr.pos = 0
- }
- read += nulIdx + 1
- if l > read {
- p = p[nulIdx+1:]
- nulIdx = bytes.IndexByte(p, '\x00')
- } else {
- return l, nil
- }
- }
- wr.tmp = append(wr.tmp, p...)
- return len(p), nil
-}
-
-func (wr *nulSeparatedAttributeWriter) Close() error {
- select {
- case <-wr.closed:
- return nil
- default:
- }
- close(wr.attributes)
- close(wr.closed)
- return nil
+ return newCheckAttrStdoutReader(stdOut, len(attributes))()
}
-// GitAttributeChecker creates an AttributeChecker for the given repository and provided commit ID.
+// GitAttributeChecker creates an AttributeChecker for the given repository and provided commit ID
+// to retrieve the attributes of multiple files. The AttributeChecker must be closed after use.
//
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
func (repo *Repository) GitAttributeChecker(treeish string, attributes ...string) (AttributeChecker, error) {
- cmd, runOpts, cancel, err := repo.gitCheckAttrCommand(treeish, attributes...)
+ cmd, runOpts, removeTempFiles, err := repo.gitCheckAttrCommand(treeish, attributes...)
if err != nil {
return AttributeChecker{}, err
}
- ac := AttributeChecker{
- attributeNumber: len(attributes),
- ctx: cmd.parentContext,
- cancel: cancel, // will be cancelled on Close
- }
+ cmd.AddArguments("--stdin")
- stdinReader, stdinWriter, err := os.Pipe()
+ // os.Pipe is needed (and not io.Pipe), otherwise cmd.Wait will wait for the stdinReader
+ // to be closed before returning (which would require another goroutine)
+ // https://go.dev/issue/23019
+ stdinReader, stdinWriter, err := os.Pipe() // reader closed in goroutine / writer closed on ac.Close
if err != nil {
- ac.cancel()
return AttributeChecker{}, err
}
- ac.stdinWriter = stdinWriter // will be closed on Close
+ stdoutReader, stdoutWriter := io.Pipe() // closed in goroutine
- lw := new(nulSeparatedAttributeWriter)
- lw.attributes = make(chan attributeTriple, len(attributes))
- lw.closed = make(chan struct{})
- ac.attributesCh = lw.attributes
+ ac := AttributeChecker{
+ removeTempFiles: removeTempFiles, // called on ac.Close
+ stdinWriter: stdinWriter,
+ readStdout: newCheckAttrStdoutReader(stdoutReader, len(attributes)),
+ err: &atomic.Value{},
+ }
- cmd.AddArguments("--stdin")
go func() {
defer stdinReader.Close()
- defer lw.Close()
+ defer stdoutWriter.Close() // in case of a panic (no-op if already closed by CloseWithError at the end)
stdErr := new(bytes.Buffer)
runOpts.Stdin = stdinReader
- runOpts.Stdout = lw
+ runOpts.Stdout = stdoutWriter
runOpts.Stderr = stdErr
+
err := cmd.Run(runOpts)
- if err != nil && // If there is an error we need to return but:
- cmd.parentContext.Err() != err && // 1. Ignore the context error if the context is cancelled or exceeds the deadline (RunWithContext could return c.ctx.Err() which is Canceled or DeadlineExceeded)
- err.Error() != "signal: killed" { // 2. We should not pass up errors due to the program being killed
- log.Error("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String())
+ // if the context was cancelled, Run error is irrelevant
+ if e := cmd.parentContext.Err(); e != nil {
+ err = e
}
+
+ if err != nil { // decorate the returned error
+ err = fmt.Errorf("git check-attr (stderr: %q): %w", strings.TrimSpace(stdErr.String()), err)
+ ac.err.Store(err)
+ }
+ stdoutWriter.CloseWithError(err)
}()
return ac, nil
}
type AttributeChecker struct {
- ctx context.Context
- cancel context.CancelFunc
- stdinWriter *os.File
- attributeNumber int
- attributesCh <-chan attributeTriple
+ removeTempFiles context.CancelFunc
+ stdinWriter io.WriteCloser
+ readStdout func() (map[string]GitAttribute, error)
+ err *atomic.Value
}
func (ac AttributeChecker) CheckPath(path string) (map[string]GitAttribute, error) {
- if err := ac.ctx.Err(); err != nil {
- return nil, err
- }
-
if _, err := ac.stdinWriter.Write([]byte(path + "\x00")); err != nil {
- return nil, err
- }
-
- rs := make(map[string]GitAttribute)
- for i := 0; i < ac.attributeNumber; i++ {
- select {
- case attr, ok := <-ac.attributesCh:
- if !ok {
- return nil, ac.ctx.Err()
- }
- rs[attr.Attribute] = GitAttribute(attr.Value)
- case <-ac.ctx.Done():
- return nil, ac.ctx.Err()
+ // try to return the Run error if available, since it is likely more helpful
+ // than just "broken pipe"
+ if aerr, _ := ac.err.Load().(error); aerr != nil {
+ return nil, aerr
}
+ return nil, fmt.Errorf("git check-attr: %w", err)
}
- return rs, nil
+
+ return ac.readStdout()
}
func (ac AttributeChecker) Close() error {
- ac.cancel()
+ ac.removeTempFiles()
return ac.stdinWriter.Close()
}
diff --git a/modules/git/repo_attribute_test.go b/modules/git/repo_attribute_test.go
index 84d15bb983..ef02f2de72 100644
--- a/modules/git/repo_attribute_test.go
+++ b/modules/git/repo_attribute_test.go
@@ -4,7 +4,14 @@
package git
import (
+ "context"
+ "fmt"
+ "io"
+ "io/fs"
+ "os"
"path/filepath"
+ "runtime"
+ "strings"
"testing"
"time"
@@ -14,90 +21,63 @@ import (
"github.com/stretchr/testify/require"
)
-func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
- wr := &nulSeparatedAttributeWriter{
- attributes: make(chan attributeTriple, 5),
- }
-
- testStr := ".gitignore\"\n\x00linguist-vendored\x00unspecified\x00"
-
- n, err := wr.Write([]byte(testStr))
-
- assert.Len(t, testStr, n)
- assert.NoError(t, err)
- select {
- case attr := <-wr.attributes:
- assert.Equal(t, ".gitignore\"\n", attr.Filename)
- assert.Equal(t, "linguist-vendored", attr.Attribute)
- assert.Equal(t, "unspecified", attr.Value)
- case <-time.After(100 * time.Millisecond):
- assert.FailNow(t, "took too long to read an attribute from the list")
- }
- // Write a second attribute again
- n, err = wr.Write([]byte(testStr))
+func TestNewCheckAttrStdoutReader(t *testing.T) {
+ t.Run("two_times", func(t *testing.T) {
+ read := newCheckAttrStdoutReader(strings.NewReader(
+ ".gitignore\x00linguist-vendored\x00unspecified\x00"+
+ ".gitignore\x00linguist-vendored\x00specified",
+ ), 1)
- assert.Len(t, testStr, n)
- assert.NoError(t, err)
-
- select {
- case attr := <-wr.attributes:
- assert.Equal(t, ".gitignore\"\n", attr.Filename)
- assert.Equal(t, "linguist-vendored", attr.Attribute)
- assert.Equal(t, "unspecified", attr.Value)
- case <-time.After(100 * time.Millisecond):
- assert.FailNow(t, "took too long to read an attribute from the list")
- }
-
- // Write a partial attribute
- _, err = wr.Write([]byte("incomplete-file"))
- assert.NoError(t, err)
- _, err = wr.Write([]byte("name\x00"))
- assert.NoError(t, err)
+ // first read
+ attr, err := read()
+ assert.NoError(t, err)
+ assert.Equal(t, map[string]GitAttribute{
+ "linguist-vendored": GitAttribute("unspecified"),
+ }, attr)
- select {
- case <-wr.attributes:
- assert.FailNow(t, "There should not be an attribute ready to read")
- case <-time.After(100 * time.Millisecond):
- }
- _, err = wr.Write([]byte("attribute\x00"))
- assert.NoError(t, err)
- select {
- case <-wr.attributes:
- assert.FailNow(t, "There should not be an attribute ready to read")
- case <-time.After(100 * time.Millisecond):
- }
+ // second read
+ attr, err = read()
+ assert.NoError(t, err)
+ assert.Equal(t, map[string]GitAttribute{
+ "linguist-vendored": GitAttribute("specified"),
+ }, attr)
+ })
+ t.Run("incomplete", func(t *testing.T) {
+ read := newCheckAttrStdoutReader(strings.NewReader(
+ "filename\x00linguist-vendored",
+ ), 1)
- _, err = wr.Write([]byte("value\x00"))
- assert.NoError(t, err)
+ _, err := read()
+ assert.Equal(t, io.ErrUnexpectedEOF, err)
+ })
+ t.Run("three_times", func(t *testing.T) {
+ read := newCheckAttrStdoutReader(strings.NewReader(
+ "shouldbe.vendor\x00linguist-vendored\x00set\x00"+
+ "shouldbe.vendor\x00linguist-generated\x00unspecified\x00"+
+ "shouldbe.vendor\x00linguist-language\x00unspecified\x00",
+ ), 1)
+
+ // first read
+ attr, err := read()
+ assert.NoError(t, err)
+ assert.Equal(t, map[string]GitAttribute{
+ "linguist-vendored": GitAttribute("set"),
+ }, attr)
- attr := <-wr.attributes
- assert.Equal(t, "incomplete-filename", attr.Filename)
- assert.Equal(t, "attribute", attr.Attribute)
- assert.Equal(t, "value", attr.Value)
+ // second read
+ attr, err = read()
+ assert.NoError(t, err)
+ assert.Equal(t, map[string]GitAttribute{
+ "linguist-generated": GitAttribute("unspecified"),
+ }, attr)
- _, err = wr.Write([]byte("shouldbe.vendor\x00linguist-vendored\x00set\x00shouldbe.vendor\x00linguist-generated\x00unspecified\x00shouldbe.vendor\x00linguist-language\x00unspecified\x00"))
- assert.NoError(t, err)
- attr = <-wr.attributes
- assert.NoError(t, err)
- assert.EqualValues(t, attributeTriple{
- Filename: "shouldbe.vendor",
- Attribute: "linguist-vendored",
- Value: "set",
- }, attr)
- attr = <-wr.attributes
- assert.NoError(t, err)
- assert.EqualValues(t, attributeTriple{
- Filename: "shouldbe.vendor",
- Attribute: "linguist-generated",
- Value: "unspecified",
- }, attr)
- attr = <-wr.attributes
- assert.NoError(t, err)
- assert.EqualValues(t, attributeTriple{
- Filename: "shouldbe.vendor",
- Attribute: "linguist-language",
- Value: "unspecified",
- }, attr)
+ // third read
+ attr, err = read()
+ assert.NoError(t, err)
+ assert.Equal(t, map[string]GitAttribute{
+ "linguist-language": GitAttribute("unspecified"),
+ }, attr)
+ })
}
func TestGitAttributeBareNonBare(t *testing.T) {
@@ -114,33 +94,35 @@ func TestGitAttributeBareNonBare(t *testing.T) {
"8fee858da5796dfb37704761701bb8e800ad9ef3",
"341fca5b5ea3de596dc483e54c2db28633cd2f97",
} {
- t.Run("GitAttributeChecker/"+commitID, func(t *testing.T) {
+ bareStats, err := gitRepo.GitAttributes(commitID, "i-am-a-python.p", LinguistAttributes...)
+ assert.NoError(t, err)
+
+ defer test.MockVariableValue(&SupportCheckAttrOnBare, false)()
+ cloneStats, err := gitRepo.GitAttributes(commitID, "i-am-a-python.p", LinguistAttributes...)
+ assert.NoError(t, err)
+
+ assert.EqualValues(t, cloneStats, bareStats)
+ refStats := cloneStats
+
+ t.Run("GitAttributeChecker/"+commitID+"/SupportBare", func(t *testing.T) {
bareChecker, err := gitRepo.GitAttributeChecker(commitID, LinguistAttributes...)
assert.NoError(t, err)
- t.Cleanup(func() { bareChecker.Close() })
+ defer bareChecker.Close()
bareStats, err := bareChecker.CheckPath("i-am-a-python.p")
assert.NoError(t, err)
-
+ assert.EqualValues(t, refStats, bareStats)
+ })
+ t.Run("GitAttributeChecker/"+commitID+"/NoBareSupport", func(t *testing.T) {
defer test.MockVariableValue(&SupportCheckAttrOnBare, false)()
cloneChecker, err := gitRepo.GitAttributeChecker(commitID, LinguistAttributes...)
assert.NoError(t, err)
- t.Cleanup(func() { cloneChecker.Close() })
- cloneStats, err := cloneChecker.CheckPath("i-am-a-python.p")
- assert.NoError(t, err)
-
- assert.EqualValues(t, cloneStats, bareStats)
- })
-
- t.Run("GitAttributes/"+commitID, func(t *testing.T) {
- bareStats, err := gitRepo.GitAttributes(commitID, "i-am-a-python.p", LinguistAttributes...)
- assert.NoError(t, err)
+ defer cloneChecker.Close()
- defer test.MockVariableValue(&SupportCheckAttrOnBare, false)()
- cloneStats, err := gitRepo.GitAttributes(commitID, "i-am-a-python.p", LinguistAttributes...)
+ cloneStats, err := cloneChecker.CheckPath("i-am-a-python.p")
assert.NoError(t, err)
- assert.EqualValues(t, cloneStats, bareStats)
+ assert.EqualValues(t, refStats, cloneStats)
})
}
}
@@ -208,3 +190,157 @@ func TestGitAttributeStruct(t *testing.T) {
assert.Equal(t, "text?token=Error", GitAttribute("text?token=Error").String())
assert.Equal(t, "text", GitAttribute("text?token=Error").Prefix())
}
+
+func TestGitAttributeCheckerError(t *testing.T) {
+ prepareRepo := func(t *testing.T) *Repository {
+ t.Helper()
+ path := t.TempDir()
+
+ // we can't use unittest.CopyDir because of an import cycle (git.Init in unittest)
+ require.NoError(t, CopyFS(path, os.DirFS(filepath.Join(testReposDir, "language_stats_repo"))))
+
+ gitRepo, err := openRepositoryWithDefaultContext(path)
+ require.NoError(t, err)
+ return gitRepo
+ }
+
+ t.Run("RemoveAll/BeforeRun", func(t *testing.T) {
+ gitRepo := prepareRepo(t)
+ defer gitRepo.Close()
+
+ assert.NoError(t, os.RemoveAll(gitRepo.Path))
+
+ ac, err := gitRepo.GitAttributeChecker("", "linguist-language")
+ require.NoError(t, err)
+
+ _, err = ac.CheckPath("i-am-a-python.p")
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), `git check-attr (stderr: ""):`)
+ })
+
+ t.Run("RemoveAll/DuringRun", func(t *testing.T) {
+ gitRepo := prepareRepo(t)
+ defer gitRepo.Close()
+
+ ac, err := gitRepo.GitAttributeChecker("", "linguist-language")
+ require.NoError(t, err)
+
+ // calling CheckPath before would allow git to cache part of it and succesfully return later
+ assert.NoError(t, os.RemoveAll(gitRepo.Path))
+
+ _, err = ac.CheckPath("i-am-a-python.p")
+ assert.Error(t, err)
+ // Depending on the order of execution, the returned error can be:
+ // - a launch error "fork/exec /usr/bin/git: no such file or directory" (when the removal happens before the Run)
+ // - a git error (stderr: "fatal: Unable to read current working directory: No such file or directory"): exit status 128 (when the removal happens after the Run)
+ // (pipe error "write |1: broken pipe" should be replaced by one of the Run errors above)
+ assert.Contains(t, err.Error(), `git check-attr`)
+ })
+
+ t.Run("Cancelled/BeforeRun", func(t *testing.T) {
+ gitRepo := prepareRepo(t)
+ defer gitRepo.Close()
+
+ var cancel context.CancelFunc
+ gitRepo.Ctx, cancel = context.WithCancel(gitRepo.Ctx)
+ cancel()
+
+ ac, err := gitRepo.GitAttributeChecker("8fee858da5796dfb37704761701bb8e800ad9ef3", "linguist-language")
+ require.NoError(t, err)
+
+ _, err = ac.CheckPath("i-am-a-python.p")
+ assert.ErrorIs(t, err, context.Canceled)
+ })
+
+ t.Run("Cancelled/DuringRun", func(t *testing.T) {
+ gitRepo := prepareRepo(t)
+ defer gitRepo.Close()
+
+ var cancel context.CancelFunc
+ gitRepo.Ctx, cancel = context.WithCancel(gitRepo.Ctx)
+
+ ac, err := gitRepo.GitAttributeChecker("8fee858da5796dfb37704761701bb8e800ad9ef3", "linguist-language")
+ require.NoError(t, err)
+
+ attr, err := ac.CheckPath("i-am-a-python.p")
+ assert.NoError(t, err)
+ assert.Equal(t, "Python", attr["linguist-language"].String())
+
+ errCh := make(chan error)
+ go func() {
+ cancel()
+
+ for err == nil {
+ _, err = ac.CheckPath("i-am-a-python.p")
+ runtime.Gosched() // the cancellation must have time to propagate
+ }
+ errCh <- err
+ }()
+
+ select {
+ case <-time.After(time.Second):
+ t.Error("CheckPath did not complete within 1s")
+ case err = <-errCh:
+ assert.ErrorIs(t, err, context.Canceled)
+ }
+ })
+
+ t.Run("Closed/BeforeRun", func(t *testing.T) {
+ gitRepo := prepareRepo(t)
+ defer gitRepo.Close()
+
+ ac, err := gitRepo.GitAttributeChecker("8fee858da5796dfb37704761701bb8e800ad9ef3", "linguist-language")
+ require.NoError(t, err)
+
+ assert.NoError(t, ac.Close())
+
+ _, err = ac.CheckPath("i-am-a-python.p")
+ assert.ErrorIs(t, err, fs.ErrClosed)
+ })
+
+ t.Run("Closed/DuringRun", func(t *testing.T) {
+ gitRepo := prepareRepo(t)
+ defer gitRepo.Close()
+
+ ac, err := gitRepo.GitAttributeChecker("8fee858da5796dfb37704761701bb8e800ad9ef3", "linguist-language")
+ require.NoError(t, err)
+
+ attr, err := ac.CheckPath("i-am-a-python.p")
+ assert.NoError(t, err)
+ assert.Equal(t, "Python", attr["linguist-language"].String())
+
+ assert.NoError(t, ac.Close())
+
+ _, err = ac.CheckPath("i-am-a-python.p")
+ assert.ErrorIs(t, err, fs.ErrClosed)
+ })
+}
+
+// CopyFS is adapted from https://github.com/golang/go/issues/62484
+// which should be available with go1.23
+func CopyFS(dir string, fsys fs.FS) error {
+ return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, _ error) error {
+ targ := filepath.Join(dir, filepath.FromSlash(path))
+ if d.IsDir() {
+ return os.MkdirAll(targ, 0o777)
+ }
+ r, err := fsys.Open(path)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+ info, err := r.Stat()
+ if err != nil {
+ return err
+ }
+ w, err := os.OpenFile(targ, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o666|info.Mode()&0o777)
+ if err != nil {
+ return err
+ }
+ if _, err := io.Copy(w, r); err != nil {
+ w.Close()
+ return fmt.Errorf("copying %s: %v", path, err)
+ }
+ return w.Close()
+ })
+}
diff --git a/modules/gitgraph/graph_models.go b/modules/gitgraph/graph_models.go
index e48fef8b9d..e9c1001964 100644
--- a/modules/gitgraph/graph_models.go
+++ b/modules/gitgraph/graph_models.go
@@ -238,7 +238,7 @@ func newRefsFromRefNames(refNames []byte) []git.Reference {
type Commit struct {
Commit *git.Commit
User *user_model.User
- Verification *asymkey_model.CommitVerification
+ Verification *asymkey_model.ObjectVerification
Status *git_model.CommitStatus
Flow int64
Row int
diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go
index 3f1115066a..077eac64f3 100644
--- a/modules/graceful/manager.go
+++ b/modules/graceful/manager.go
@@ -136,7 +136,7 @@ func (g *Manager) doShutdown() {
}
g.lock.Lock()
g.shutdownCtxCancel()
- atShutdownCtx := pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "post-shutdown"))
+ atShutdownCtx := pprof.WithLabels(g.hammerCtx, pprof.Labels("gracefulLifecycle", "post-shutdown"))
pprof.SetGoroutineLabels(atShutdownCtx)
for _, fn := range g.toRunAtShutdown {
go fn()
@@ -167,7 +167,7 @@ func (g *Manager) doHammerTime(d time.Duration) {
default:
log.Warn("Setting Hammer condition")
g.hammerCtxCancel()
- atHammerCtx := pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "post-hammer"))
+ atHammerCtx := pprof.WithLabels(g.terminateCtx, pprof.Labels("gracefulLifecycle", "post-hammer"))
pprof.SetGoroutineLabels(atHammerCtx)
}
g.lock.Unlock()
@@ -183,7 +183,7 @@ func (g *Manager) doTerminate() {
default:
log.Warn("Terminating")
g.terminateCtxCancel()
- atTerminateCtx := pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "post-terminate"))
+ atTerminateCtx := pprof.WithLabels(g.managerCtx, pprof.Labels("gracefulLifecycle", "post-terminate"))
pprof.SetGoroutineLabels(atTerminateCtx)
for _, fn := range g.toRunAtTerminate {
diff --git a/modules/graceful/manager_common.go b/modules/graceful/manager_common.go
index f6dbcc748d..892957e93f 100644
--- a/modules/graceful/manager_common.go
+++ b/modules/graceful/manager_common.go
@@ -65,10 +65,10 @@ func (g *Manager) prepare(ctx context.Context) {
g.hammerCtx, g.hammerCtxCancel = context.WithCancel(ctx)
g.managerCtx, g.managerCtxCancel = context.WithCancel(ctx)
- g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate"))
- g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown"))
- g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer"))
- g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager"))
+ g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("gracefulLifecycle", "with-terminate"))
+ g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("gracefulLifecycle", "with-shutdown"))
+ g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("gracefulLifecycle", "with-hammer"))
+ g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("gracefulLifecycle", "with-manager"))
if !g.setStateTransition(stateInit, stateRunning) {
panic("invalid graceful manager state: transition from init to running failed")
diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go
index d03fff9b5b..931b0f1b62 100644
--- a/modules/graceful/manager_unix.go
+++ b/modules/graceful/manager_unix.go
@@ -44,7 +44,7 @@ func (g *Manager) notify(msg systemdNotifyMsg) {
}
func (g *Manager) start() {
- // Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
+ // Now label this and all goroutines created by this goroutine with the gracefulLifecycle manager
pprof.SetGoroutineLabels(g.managerCtx)
defer pprof.SetGoroutineLabels(g.ctx)
diff --git a/modules/graceful/manager_windows.go b/modules/graceful/manager_windows.go
index d776e0e9f9..bee44381db 100644
--- a/modules/graceful/manager_windows.go
+++ b/modules/graceful/manager_windows.go
@@ -29,7 +29,7 @@ const (
)
func (g *Manager) start() {
- // Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
+ // Now label this and all goroutines created by this goroutine with the gracefulLifecycle manager
pprof.SetGoroutineLabels(g.managerCtx)
defer pprof.SetGoroutineLabels(g.ctx)
diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go
index d7ab3f7afd..4ee47b7a13 100644
--- a/modules/highlight/highlight.go
+++ b/modules/highlight/highlight.go
@@ -216,8 +216,8 @@ func PlainText(code []byte) []template.HTML {
}
func formatLexerName(name string) string {
- if name == "fallback" {
- return "Plaintext"
+ if name == "fallback" || name == "plaintext" {
+ return "Text"
}
return util.ToTitleCaseNoLower(name)
diff --git a/modules/highlight/highlight_test.go b/modules/highlight/highlight_test.go
index 659688bd0f..dd15b97847 100644
--- a/modules/highlight/highlight_test.go
+++ b/modules/highlight/highlight_test.go
@@ -58,7 +58,7 @@ func TestFile(t *testing.T) {
name: "tags.txt",
code: "<>",
want: lines("&lt;&gt;"),
- lexerName: "Plaintext",
+ lexerName: "Text",
},
{
name: "tags.py",
diff --git a/modules/indexer/internal/paginator.go b/modules/indexer/internal/paginator.go
index de0a33c06f..ee204bf047 100644
--- a/modules/indexer/internal/paginator.go
+++ b/modules/indexer/internal/paginator.go
@@ -10,7 +10,7 @@ import (
)
// ParsePaginator parses a db.Paginator into a skip and limit
-func ParsePaginator(paginator db.Paginator, max ...int) (int, int) {
+func ParsePaginator(paginator *db.ListOptions, max ...int) (int, int) {
// Use a very large number to indicate no limit
unlimited := math.MaxInt32
if len(max) > 0 {
@@ -19,22 +19,15 @@ func ParsePaginator(paginator db.Paginator, max ...int) (int, int) {
}
if paginator == nil || paginator.IsListAll() {
+ // It shouldn't happen. In actual usage scenarios, there should not be requests to search all.
+ // But if it does happen, respect it and return "unlimited".
+ // And it's also useful for testing.
return 0, unlimited
}
- // Warning: Do not use GetSkipTake() for *db.ListOptions
- // Its implementation could reset the page size with setting.API.MaxResponseItems
- if listOptions, ok := paginator.(*db.ListOptions); ok {
- if listOptions.Page >= 0 && listOptions.PageSize > 0 {
- var start int
- if listOptions.Page == 0 {
- start = 0
- } else {
- start = (listOptions.Page - 1) * listOptions.PageSize
- }
- return start, listOptions.PageSize
- }
- return 0, unlimited
+ if paginator.PageSize == 0 {
+ // Do not return any results when searching, it's used to get the total count only.
+ return 0, 0
}
return paginator.GetSkipTake()
diff --git a/modules/indexer/issues/db/db.go b/modules/indexer/issues/db/db.go
index 1016523b72..05ec548435 100644
--- a/modules/indexer/issues/db/db.go
+++ b/modules/indexer/issues/db/db.go
@@ -78,6 +78,17 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
return nil, err
}
+ // If pagesize == 0, return total count only. It's a special case for search count.
+ if options.Paginator != nil && options.Paginator.PageSize == 0 {
+ total, err := issue_model.CountIssues(ctx, opt, cond)
+ if err != nil {
+ return nil, err
+ }
+ return &internal.SearchResult{
+ Total: total,
+ }, nil
+ }
+
ids, total, err := issue_model.IssueIDs(ctx, opt, cond)
if err != nil {
return nil, err
diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go
index e3bc21b49d..1cb86feb82 100644
--- a/modules/indexer/issues/indexer.go
+++ b/modules/indexer/issues/indexer.go
@@ -308,7 +308,7 @@ func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, err
// CountIssues counts issues by options. It is a shortcut of SearchIssues(ctx, opts) but only returns the total count.
func CountIssues(ctx context.Context, opts *SearchOptions) (int64, error) {
- opts = opts.Copy(func(options *SearchOptions) { opts.Paginator = &db_model.ListOptions{PageSize: 0} })
+ opts = opts.Copy(func(options *SearchOptions) { options.Paginator = &db_model.ListOptions{PageSize: 0} })
_, total, err := SearchIssues(ctx, opts)
return total, err
diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go
index b7102c35af..e9c4eca559 100644
--- a/modules/indexer/issues/internal/model.go
+++ b/modules/indexer/issues/internal/model.go
@@ -106,7 +106,7 @@ type SearchOptions struct {
UpdatedAfterUnix optional.Option[int64]
UpdatedBeforeUnix optional.Option[int64]
- db.Paginator
+ Paginator *db.ListOptions
SortBy SortBy // sort by field
}
diff --git a/modules/indexer/issues/internal/tests/tests.go b/modules/indexer/issues/internal/tests/tests.go
index 2209377c2f..7f32876d80 100644
--- a/modules/indexer/issues/internal/tests/tests.go
+++ b/modules/indexer/issues/internal/tests/tests.go
@@ -77,6 +77,13 @@ func TestIndexer(t *testing.T, indexer internal.Indexer) {
assert.Equal(t, c.ExpectedIDs, ids)
assert.Equal(t, c.ExpectedTotal, result.Total)
}
+
+ // test counting
+ c.SearchOptions.Paginator = &db.ListOptions{PageSize: 0}
+ countResult, err := indexer.Search(context.Background(), c.SearchOptions)
+ require.NoError(t, err)
+ assert.Empty(t, countResult.Hits)
+ assert.Equal(t, result.Total, countResult.Total)
})
}
}
diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go
index b735c26968..8a7cec6cba 100644
--- a/modules/indexer/issues/meilisearch/meilisearch.go
+++ b/modules/indexer/issues/meilisearch/meilisearch.go
@@ -218,6 +218,14 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
skip, limit := indexer_internal.ParsePaginator(options.Paginator, maxTotalHits)
+ counting := limit == 0
+ if counting {
+ // If set limit to 0, it will be 20 by default, and -1 is not allowed.
+ // See https://www.meilisearch.com/docs/reference/api/search#limit
+ // So set limit to 1 to make the cost as low as possible, then clear the result before returning.
+ limit = 1
+ }
+
keyword := options.Keyword
if !options.IsFuzzyKeyword {
// to make it non fuzzy ("typo tolerance" in meilisearch terms), we have to quote the keyword(s)
@@ -236,6 +244,10 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
return nil, err
}
+ if counting {
+ searchRes.Hits = nil
+ }
+
hits, err := convertHits(searchRes)
if err != nil {
return nil, err
diff --git a/modules/markup/file_preview.go b/modules/markup/file_preview.go
new file mode 100644
index 0000000000..95c94e0c14
--- /dev/null
+++ b/modules/markup/file_preview.go
@@ -0,0 +1,323 @@
+// Copyright The Forgejo Authors.
+// SPDX-License-Identifier: MIT
+
+package markup
+
+import (
+ "bufio"
+ "bytes"
+ "html/template"
+ "regexp"
+ "slices"
+ "strconv"
+ "strings"
+
+ "code.gitea.io/gitea/modules/charset"
+ "code.gitea.io/gitea/modules/highlight"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/translation"
+
+ "golang.org/x/net/html"
+ "golang.org/x/net/html/atom"
+)
+
+// filePreviewPattern matches "http://domain/org/repo/src/commit/COMMIT/filepath#L1-L2"
+var filePreviewPattern = regexp.MustCompile(`https?://((?:\S+/){3})src/commit/([0-9a-f]{4,64})/(\S+)#(L\d+(?:-L\d+)?)`)
+
+type FilePreview struct {
+ fileContent []template.HTML
+ subTitle template.HTML
+ lineOffset int
+ urlFull string
+ filePath string
+ start int
+ end int
+ isTruncated bool
+}
+
+func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Locale) *FilePreview {
+ if setting.FilePreviewMaxLines == 0 {
+ // Feature is disabled
+ return nil
+ }
+
+ preview := &FilePreview{}
+
+ m := filePreviewPattern.FindStringSubmatchIndex(node.Data)
+ if m == nil {
+ return nil
+ }
+
+ // Ensure that every group has a match
+ if slices.Contains(m, -1) {
+ return nil
+ }
+
+ preview.urlFull = node.Data[m[0]:m[1]]
+
+ // Ensure that we only use links to local repositories
+ if !strings.HasPrefix(preview.urlFull, setting.AppURL+setting.AppSubURL) {
+ return nil
+ }
+
+ projPath := strings.TrimSuffix(node.Data[m[2]:m[3]], "/")
+
+ commitSha := node.Data[m[4]:m[5]]
+ preview.filePath = node.Data[m[6]:m[7]]
+ hash := node.Data[m[8]:m[9]]
+
+ preview.start = m[0]
+ preview.end = m[1]
+
+ projPathSegments := strings.Split(projPath, "/")
+ var language string
+ fileBlob, err := DefaultProcessorHelper.GetRepoFileBlob(
+ ctx.Ctx,
+ projPathSegments[len(projPathSegments)-2],
+ projPathSegments[len(projPathSegments)-1],
+ commitSha, preview.filePath,
+ &language,
+ )
+ if err != nil {
+ return nil
+ }
+
+ lineSpecs := strings.Split(hash, "-")
+
+ commitLinkBuffer := new(bytes.Buffer)
+ err = html.Render(commitLinkBuffer, createLink(node.Data[m[0]:m[5]], commitSha[0:7], "text black"))
+ if err != nil {
+ log.Error("failed to render commitLink: %v", err)
+ }
+
+ var startLine, endLine int
+
+ if len(lineSpecs) == 1 {
+ startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
+ endLine = startLine
+ preview.subTitle = locale.Tr(
+ "markup.filepreview.line", startLine,
+ template.HTML(commitLinkBuffer.String()),
+ )
+
+ preview.lineOffset = startLine - 1
+ } else {
+ startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
+ endLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[1], "L"))
+ preview.subTitle = locale.Tr(
+ "markup.filepreview.lines", startLine, endLine,
+ template.HTML(commitLinkBuffer.String()),
+ )
+
+ preview.lineOffset = startLine - 1
+ }
+
+ lineCount := endLine - (startLine - 1)
+ if startLine < 1 || endLine < 1 || lineCount < 1 {
+ return nil
+ }
+
+ if setting.FilePreviewMaxLines > 0 && lineCount > setting.FilePreviewMaxLines {
+ preview.isTruncated = true
+ lineCount = setting.FilePreviewMaxLines
+ }
+
+ dataRc, err := fileBlob.DataAsync()
+ if err != nil {
+ return nil
+ }
+ defer dataRc.Close()
+
+ reader := bufio.NewReader(dataRc)
+
+ // skip all lines until we find our startLine
+ for i := 1; i < startLine; i++ {
+ _, err := reader.ReadBytes('\n')
+ if err != nil {
+ return nil
+ }
+ }
+
+ // capture the lines we're interested in
+ lineBuffer := new(bytes.Buffer)
+ for i := 0; i < lineCount; i++ {
+ buf, err := reader.ReadBytes('\n')
+ if err != nil {
+ break
+ }
+ lineBuffer.Write(buf)
+ }
+
+ // highlight the file...
+ fileContent, _, err := highlight.File(fileBlob.Name(), language, lineBuffer.Bytes())
+ if err != nil {
+ log.Error("highlight.File failed, fallback to plain text: %v", err)
+ fileContent = highlight.PlainText(lineBuffer.Bytes())
+ }
+ preview.fileContent = fileContent
+
+ return preview
+}
+
+func (p *FilePreview) CreateHTML(locale translation.Locale) *html.Node {
+ table := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Table.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "file-preview"}},
+ }
+ tbody := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Tbody.String(),
+ }
+
+ status := &charset.EscapeStatus{}
+ statuses := make([]*charset.EscapeStatus, len(p.fileContent))
+ for i, line := range p.fileContent {
+ statuses[i], p.fileContent[i] = charset.EscapeControlHTML(line, locale, charset.FileviewContext)
+ status = status.Or(statuses[i])
+ }
+
+ for idx, code := range p.fileContent {
+ tr := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Tr.String(),
+ }
+
+ lineNum := strconv.Itoa(p.lineOffset + idx + 1)
+
+ tdLinesnum := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Td.String(),
+ Attr: []html.Attribute{
+ {Key: "class", Val: "lines-num"},
+ },
+ }
+ spanLinesNum := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Span.String(),
+ Attr: []html.Attribute{
+ {Key: "data-line-number", Val: lineNum},
+ },
+ }
+ tdLinesnum.AppendChild(spanLinesNum)
+ tr.AppendChild(tdLinesnum)
+
+ if status.Escaped {
+ tdLinesEscape := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Td.String(),
+ Attr: []html.Attribute{
+ {Key: "class", Val: "lines-escape"},
+ },
+ }
+
+ if statuses[idx].Escaped {
+ btnTitle := ""
+ if statuses[idx].HasInvisible {
+ btnTitle += locale.TrString("repo.invisible_runes_line") + " "
+ }
+ if statuses[idx].HasAmbiguous {
+ btnTitle += locale.TrString("repo.ambiguous_runes_line")
+ }
+
+ escapeBtn := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Button.String(),
+ Attr: []html.Attribute{
+ {Key: "class", Val: "toggle-escape-button btn interact-bg"},
+ {Key: "title", Val: btnTitle},
+ },
+ }
+ tdLinesEscape.AppendChild(escapeBtn)
+ }
+
+ tr.AppendChild(tdLinesEscape)
+ }
+
+ tdCode := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Td.String(),
+ Attr: []html.Attribute{
+ {Key: "class", Val: "lines-code chroma"},
+ },
+ }
+ codeInner := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Code.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "code-inner"}},
+ }
+ codeText := &html.Node{
+ Type: html.RawNode,
+ Data: string(code),
+ }
+ codeInner.AppendChild(codeText)
+ tdCode.AppendChild(codeInner)
+ tr.AppendChild(tdCode)
+
+ tbody.AppendChild(tr)
+ }
+
+ table.AppendChild(tbody)
+
+ twrapper := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Div.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "ui table"}},
+ }
+ twrapper.AppendChild(table)
+
+ header := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Div.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "header"}},
+ }
+ afilepath := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.A.String(),
+ Attr: []html.Attribute{
+ {Key: "href", Val: p.urlFull},
+ {Key: "class", Val: "muted"},
+ },
+ }
+ afilepath.AppendChild(&html.Node{
+ Type: html.TextNode,
+ Data: p.filePath,
+ })
+ header.AppendChild(afilepath)
+
+ psubtitle := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Span.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "text small grey"}},
+ }
+ psubtitle.AppendChild(&html.Node{
+ Type: html.RawNode,
+ Data: string(p.subTitle),
+ })
+ header.AppendChild(psubtitle)
+
+ node := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Div.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "file-preview-box"}},
+ }
+ node.AppendChild(header)
+
+ if p.isTruncated {
+ warning := &html.Node{
+ Type: html.ElementNode,
+ Data: atom.Div.String(),
+ Attr: []html.Attribute{{Key: "class", Val: "ui warning message tw-text-left"}},
+ }
+ warning.AppendChild(&html.Node{
+ Type: html.TextNode,
+ Data: locale.TrString("markup.filepreview.truncated"),
+ })
+ node.AppendChild(warning)
+ }
+
+ node.AppendChild(twrapper)
+
+ return node
+}
diff --git a/modules/markup/html.go b/modules/markup/html.go
index 0cfd8be590..5ec7484eb4 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -171,6 +171,7 @@ type processor func(ctx *RenderContext, node *html.Node)
var defaultProcessors = []processor{
fullIssuePatternProcessor,
comparePatternProcessor,
+ filePreviewPatternProcessor,
fullHashPatternProcessor,
shortLinkProcessor,
linkProcessor,
@@ -1054,6 +1055,47 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
}
}
+func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
+ if ctx.Metas == nil {
+ return
+ }
+ if DefaultProcessorHelper.GetRepoFileBlob == nil {
+ return
+ }
+
+ next := node.NextSibling
+ for node != nil && node != next {
+ locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale)
+ if !ok {
+ locale = translation.NewLocale("en-US")
+ }
+
+ preview := NewFilePreview(ctx, node, locale)
+ if preview == nil {
+ return
+ }
+
+ previewNode := preview.CreateHTML(locale)
+
+ // Specialized version of replaceContent, so the parent paragraph element is not destroyed from our div
+ before := node.Data[:preview.start]
+ after := node.Data[preview.end:]
+ node.Data = before
+ nextSibling := node.NextSibling
+ node.Parent.InsertBefore(&html.Node{
+ Type: html.RawNode,
+ Data: "</p>",
+ }, nextSibling)
+ node.Parent.InsertBefore(previewNode, nextSibling)
+ node.Parent.InsertBefore(&html.Node{
+ Type: html.RawNode,
+ Data: "<p>" + after,
+ }, nextSibling)
+
+ node = node.NextSibling
+ }
+}
+
// emojiShortCodeProcessor for rendering text like :smile: into emoji
func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
start := 0
diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go
index 1db6952bed..61a3edd6b3 100644
--- a/modules/markup/html_test.go
+++ b/modules/markup/html_test.go
@@ -17,9 +17,11 @@ import (
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
var localMetas = map[string]string{
@@ -676,3 +678,68 @@ func TestIssue18471(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "<a href=\"http://domain/org/repo/compare/783b039...da951ce\" class=\"compare\"><code class=\"nohighlight\">783b039...da951ce</code></a>", res.String())
}
+
+func TestRender_FilePreview(t *testing.T) {
+ setting.StaticRootPath = "../../"
+ setting.Names = []string{"english"}
+ setting.Langs = []string{"en-US"}
+ translation.InitLocales(context.Background())
+
+ setting.AppURL = markup.TestAppURL
+ markup.Init(&markup.ProcessorHelper{
+ GetRepoFileBlob: func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error) {
+ gitRepo, err := git.OpenRepository(git.DefaultContext, "./tests/repo/repo1_filepreview")
+ require.NoError(t, err)
+ defer gitRepo.Close()
+
+ commit, err := gitRepo.GetCommit("HEAD")
+ require.NoError(t, err)
+
+ blob, err := commit.GetBlobByPath("path/to/file.go")
+ require.NoError(t, err)
+
+ return blob, nil
+ },
+ })
+
+ sha := "190d9492934af498c3f669d6a2431dc5459e5b20"
+ commitFilePreview := util.URLJoin(markup.TestRepoURL, "src", "commit", sha, "path", "to", "file.go") + "#L2-L3"
+
+ test := func(input, expected string) {
+ buffer, err := markup.RenderString(&markup.RenderContext{
+ Ctx: git.DefaultContext,
+ RelativePath: ".md",
+ Metas: localMetas,
+ }, input)
+ assert.NoError(t, err)
+ assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
+ }
+
+ test(
+ commitFilePreview,
+ `<p></p>`+
+ `<div class="file-preview-box">`+
+ `<div class="header">`+
+ `<a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+
+ `<span class="text small grey">`+
+ `Lines 2 to 3 in <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">190d949</a>`+
+ `</span>`+
+ `</div>`+
+ `<div class="ui table">`+
+ `<table class="file-preview">`+
+ `<tbody>`+
+ `<tr>`+
+ `<td class="lines-num"><span data-line-number="2"></span></td>`+
+ `<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+
+ `</tr>`+
+ `<tr>`+
+ `<td class="lines-num"><span data-line-number="3"></span></td>`+
+ `<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+
+ `</tr>`+
+ `</tbody>`+
+ `</table>`+
+ `</div>`+
+ `</div>`+
+ `<p></p>`,
+ )
+}
diff --git a/modules/markup/markdown/color_util.go b/modules/markup/markdown/color_util.go
new file mode 100644
index 0000000000..355fef3fc0
--- /dev/null
+++ b/modules/markup/markdown/color_util.go
@@ -0,0 +1,19 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package markdown
+
+import "regexp"
+
+var (
+ hexRGB = regexp.MustCompile(`^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$`)
+ hsl = regexp.MustCompile(`^hsl\([ ]*([012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%\)$`)
+ hsla = regexp.MustCompile(`^hsla\(([ ]*[012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%,[ ]*(1|1\.0|0|(0\.[0-9]+))\)$`)
+ rgb = regexp.MustCompile(`^rgb\(([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))),){2}([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))))\)$`)
+ rgba = regexp.MustCompile(`^rgba\(([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))),){3}[ ]*(1(\.0)?|0|(0\.[0-9]+))\)$`)
+)
+
+// matchColor return if color is in the form of hex RGB, HSL(A) or RGB(A).
+func matchColor(color string) bool {
+ return hexRGB.MatchString(color) || rgb.MatchString(color) || rgba.MatchString(color) || hsl.MatchString(color) || hsla.MatchString(color)
+}
diff --git a/modules/markup/markdown/color_util_test.go b/modules/markup/markdown/color_util_test.go
new file mode 100644
index 0000000000..c6e0555a35
--- /dev/null
+++ b/modules/markup/markdown/color_util_test.go
@@ -0,0 +1,50 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package markdown
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMatchColor(t *testing.T) {
+ testCases := []struct {
+ input string
+ expected bool
+ }{
+ {"#ddeeffa0", true},
+ {"#ddeefe", true},
+ {"#abcdef", true},
+ {"#abcdeg", false},
+ {"#abcdefg0", false},
+ {"black", false},
+ {"violet", false},
+ {"rgb(255, 255, 255)", true},
+ {"rgb(0, 0, 0)", true},
+ {"rgb(256, 0, 0)", false},
+ {"rgb(0, 256, 0)", false},
+ {"rgb(0, 0, 256)", false},
+ {"rgb(0, 0, 0, 1)", false},
+ {"rgba(0, 0, 0)", false},
+ {"rgba(0, 255, 0, 1)", true},
+ {"rgba(32, 255, 12, 0.55)", true},
+ {"rgba(32, 256, 12, 0.55)", false},
+ {"hsl(0, 0%, 0%)", true},
+ {"hsl(360, 100%, 100%)", true},
+ {"hsl(361, 100%, 50%)", false},
+ {"hsl(360, 101%, 50%)", false},
+ {"hsl(360, 100%, 101%)", false},
+ {"hsl(0, 0%, 0%, 0)", false},
+ {"hsla(0, 0%, 0%)", false},
+ {"hsla(0, 0%, 0%, 0)", true},
+ {"hsla(0, 0%, 0%, 1)", true},
+ {"hsla(0, 0%, 0%, 0.5)", true},
+ {"hsla(0, 0%, 0%, 1.5)", false},
+ }
+ for _, testCase := range testCases {
+ actual := matchColor(testCase.input)
+ assert.Equal(t, testCase.expected, actual)
+ }
+}
diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go
index 7ada8b5548..5a481a31fd 100644
--- a/modules/markup/markdown/goldmark.go
+++ b/modules/markup/markdown/goldmark.go
@@ -16,7 +16,6 @@ import (
"code.gitea.io/gitea/modules/setting"
giteautil "code.gitea.io/gitea/modules/util"
- "github.com/microcosm-cc/bluemonday/css"
"github.com/yuin/goldmark/ast"
east "github.com/yuin/goldmark/extension/ast"
"github.com/yuin/goldmark/parser"
@@ -199,7 +198,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
}
case *ast.CodeSpan:
colorContent := n.Text(reader.Source())
- if css.ColorHandler(strings.ToLower(string(colorContent))) {
+ if matchColor(strings.ToLower(string(colorContent))) {
v.AppendChild(v, NewColorPreview(colorContent))
}
}
diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go
index 0f0bf55740..f1beee964a 100644
--- a/modules/markup/renderer.go
+++ b/modules/markup/renderer.go
@@ -31,6 +31,7 @@ const (
type ProcessorHelper struct {
IsUsernameMentionable func(ctx context.Context, username string) bool
+ GetRepoFileBlob func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error)
ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
}
diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go
index 79a2ba0dfb..cdbb1f7d97 100644
--- a/modules/markup/sanitizer.go
+++ b/modules/markup/sanitizer.go
@@ -113,6 +113,23 @@ func createDefaultPolicy() *bluemonday.Policy {
// Allow 'color' and 'background-color' properties for the style attribute on text elements.
policy.AllowStyles("color", "background-color").OnElements("span", "p")
+ // Allow classes for file preview links...
+ policy.AllowAttrs("class").Matching(regexp.MustCompile("^(lines-num|lines-code chroma)$")).OnElements("td")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile("^code-inner$")).OnElements("code")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview-box$")).OnElements("div")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui table$")).OnElements("div")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile("^header$")).OnElements("div")
+ policy.AllowAttrs("data-line-number").Matching(regexp.MustCompile("^[0-9]+$")).OnElements("span")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile("^text small grey$")).OnElements("span")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview*")).OnElements("table")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile("^lines-escape$")).OnElements("td")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile("^toggle-escape-button btn interact-bg$")).OnElements("button")
+ policy.AllowAttrs("title").OnElements("button")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile("^ambiguous-code-point$")).OnElements("span")
+ policy.AllowAttrs("data-tooltip-content").OnElements("span")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile("muted|(text black)")).OnElements("a")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui warning message tw-text-left$")).OnElements("div")
+
// Allow generally safe attributes
generalSafeAttrs := []string{
"abbr", "accept", "accept-charset",
diff --git a/modules/markup/tests/repo/repo1_filepreview/HEAD b/modules/markup/tests/repo/repo1_filepreview/HEAD
new file mode 100644
index 0000000000..cb089cd89a
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/modules/markup/tests/repo/repo1_filepreview/config b/modules/markup/tests/repo/repo1_filepreview/config
new file mode 100644
index 0000000000..42cc799c8d
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+[remote "origin"]
+ url = /home/mai/projects/codeark/forgejo/forgejo/modules/markup/tests/repo/repo1_filepreview/../../__test_repo
diff --git a/modules/markup/tests/repo/repo1_filepreview/description b/modules/markup/tests/repo/repo1_filepreview/description
new file mode 100644
index 0000000000..498b267a8c
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/modules/markup/tests/repo/repo1_filepreview/info/exclude b/modules/markup/tests/repo/repo1_filepreview/info/exclude
new file mode 100644
index 0000000000..a5196d1be8
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/19/0d9492934af498c3f669d6a2431dc5459e5b20 b/modules/markup/tests/repo/repo1_filepreview/objects/19/0d9492934af498c3f669d6a2431dc5459e5b20
new file mode 100644
index 0000000000..161d0bafc6
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/objects/19/0d9492934af498c3f669d6a2431dc5459e5b20
Binary files differ
diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/modules/markup/tests/repo/repo1_filepreview/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
new file mode 100644
index 0000000000..adf64119a3
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
Binary files differ
diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/83/57a737d04385bb7f2ab59ff184be94756e7972 b/modules/markup/tests/repo/repo1_filepreview/objects/83/57a737d04385bb7f2ab59ff184be94756e7972
new file mode 100644
index 0000000000..1b87aa8b23
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/objects/83/57a737d04385bb7f2ab59ff184be94756e7972
Binary files differ
diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/84/22d40f12717e1ebd5cef2449f6c09d1f775969 b/modules/markup/tests/repo/repo1_filepreview/objects/84/22d40f12717e1ebd5cef2449f6c09d1f775969
new file mode 100644
index 0000000000..d38170a588
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/objects/84/22d40f12717e1ebd5cef2449f6c09d1f775969
Binary files differ
diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/d4/490327def9658be036d6a52c4417d84e74dd4c b/modules/markup/tests/repo/repo1_filepreview/objects/d4/490327def9658be036d6a52c4417d84e74dd4c
new file mode 100644
index 0000000000..fe37c11528
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/objects/d4/490327def9658be036d6a52c4417d84e74dd4c
Binary files differ
diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/ee/2b1253d9cf407796e2e724926cbe3a974b214d b/modules/markup/tests/repo/repo1_filepreview/objects/ee/2b1253d9cf407796e2e724926cbe3a974b214d
new file mode 100644
index 0000000000..e13ca647db
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/objects/ee/2b1253d9cf407796e2e724926cbe3a974b214d
@@ -0,0 +1 @@
+x+)JMU06e040031QHËÌIÕKÏghQºÂ/TX'·7潊ç·såË#3‹ô \ No newline at end of file
diff --git a/modules/markup/tests/repo/repo1_filepreview/refs/heads/master b/modules/markup/tests/repo/repo1_filepreview/refs/heads/master
new file mode 100644
index 0000000000..49c348b41c
--- /dev/null
+++ b/modules/markup/tests/repo/repo1_filepreview/refs/heads/master
@@ -0,0 +1 @@
+190d9492934af498c3f669d6a2431dc5459e5b20
diff --git a/modules/process/manager.go b/modules/process/manager.go
index 9c21f62152..150179d89b 100644
--- a/modules/process/manager.go
+++ b/modules/process/manager.go
@@ -26,7 +26,7 @@ var (
)
// DescriptionPProfLabel is a label set on goroutines that have a process attached
-const DescriptionPProfLabel = "process-description"
+const DescriptionPProfLabel = "processDescription"
// PIDPProfLabel is a label set on goroutines that have a process attached
const PIDPProfLabel = "pid"
@@ -35,7 +35,7 @@ const PIDPProfLabel = "pid"
const PPIDPProfLabel = "ppid"
// ProcessTypePProfLabel is a label set on goroutines that have a process attached
-const ProcessTypePProfLabel = "process-type"
+const ProcessTypePProfLabel = "processType"
// IDType is a pid type
type IDType string
diff --git a/modules/setting/markup.go b/modules/setting/markup.go
index 6c2246342b..e893c1c2f1 100644
--- a/modules/setting/markup.go
+++ b/modules/setting/markup.go
@@ -15,6 +15,7 @@ var (
ExternalMarkupRenderers []*MarkupRenderer
ExternalSanitizerRules []MarkupSanitizerRule
MermaidMaxSourceCharacters int
+ FilePreviewMaxLines int
)
const (
@@ -62,6 +63,7 @@ func loadMarkupFrom(rootCfg ConfigProvider) {
mustMapSetting(rootCfg, "markdown", &Markdown)
MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000)
+ FilePreviewMaxLines = rootCfg.Section("markup").Key("FILEPREVIEW_MAX_LINES").MustInt(50)
ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10)
ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10)
diff --git a/modules/setting/security.go b/modules/setting/security.go
index 380360a696..3d7b1f9ce7 100644
--- a/modules/setting/security.go
+++ b/modules/setting/security.go
@@ -103,7 +103,7 @@ func generateSaveInternalToken(rootCfg ConfigProvider) {
func loadSecurityFrom(rootCfg ConfigProvider) {
sec := rootCfg.Section("security")
InstallLock = HasInstallLock(rootCfg)
- LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7)
+ LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(31)
SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY")
if SecretKey == "" {
// FIXME: https://github.com/go-gitea/gitea/issues/16832
diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go
index cdff31698c..5deb051abe 100644
--- a/modules/templates/util_render.go
+++ b/modules/templates/util_render.go
@@ -40,7 +40,7 @@ func RenderCommitMessage(ctx context.Context, msg string, metas map[string]strin
if len(msgLines) == 0 {
return template.HTML("")
}
- return template.HTML(msgLines[0])
+ return RenderCodeBlock(template.HTML(msgLines[0]))
}
// RenderCommitMessageLinkSubject renders commit message as a XSS-safe link to
@@ -67,7 +67,7 @@ func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlDefault string,
log.Error("RenderCommitMessageSubject: %v", err)
return template.HTML("")
}
- return template.HTML(renderedMessage)
+ return RenderCodeBlock(template.HTML(renderedMessage))
}
// RenderCommitBody extracts the body of a commit message without its title.
diff --git a/modules/testlogger/testlogger.go b/modules/testlogger/testlogger.go
index c5ca906220..acabad09ad 100644
--- a/modules/testlogger/testlogger.go
+++ b/modules/testlogger/testlogger.go
@@ -43,9 +43,7 @@ func (w *testLoggerWriterCloser) pushT(t testing.TB) {
}
func (w *testLoggerWriterCloser) Log(level log.Level, msg string) {
- if len(msg) > 0 && msg[len(msg)-1] == '\n' {
- msg = msg[:len(msg)-1]
- }
+ msg = strings.TrimSpace(msg)
w.printMsg(msg)
if level >= log.ERROR {
@@ -56,10 +54,13 @@ func (w *testLoggerWriterCloser) Log(level log.Level, msg string) {
// list of error message which will not fail the test
// ideally this list should be empty, however ensuring that it does not grow
// is already a good first step.
-var ignoredErrorMessageSuffixes = []string{
+var ignoredErrorMessage = []string{
// only seen on mysql tests https://codeberg.org/forgejo/forgejo/pulls/2657#issuecomment-1693055
`table columns using inconsistent collation, they should use "utf8mb4_0900_ai_ci". Please go to admin panel Self Check page`,
+ // TestPullWIPConvertSidebar
+ `:PullRequestPushCommits() [E] comment.LoadIssue: issue does not exist [id:`,
+
// TestAPIDeleteReleaseByTagName
// action notification were a commit cannot be computed (because the commit got deleted)
`Notify() [E] an error occurred while executing the DeleteRelease actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/release-tag, rel_path: ]`,
@@ -76,6 +77,14 @@ var ignoredErrorMessageSuffixes = []string{
// TestAPIGenerateRepo
`Notify() [E] an error occurred while executing the CreateRepository actions method: gitRepo.GetCommit: object does not exist [id: , rel_path: ]`,
+ // TestAPIPullUpdateByRebase
+ `:testPR() [E] Unable to GetPullRequestByID[`,
+ `:PullRequestSynchronized() [E] LoadAttributes: getRepositoryByID `,
+ `:PullRequestSynchronized() [E] pr.Issue.LoadRepo: getRepositoryByID [`,
+ `:handler() [E] Was unable to create issue notification: issue does not exist [`,
+ `:func1() [E] PullRequestList.LoadAttributes: issues and prs may be not in sync: cannot find issue`,
+ `:func1() [E] checkForInvalidation: GetRepositoryByIDCtx: repository does not exist `,
+
// TestAPIPullReview
`PullRequestReview() [E] Unsupported review webhook type`,
@@ -111,11 +120,284 @@ var ignoredErrorMessageSuffixes = []string{
// TestRebuildCargo
`RebuildCargoIndex() [E] RebuildIndex failed: GetRepositoryByOwnerAndName: repository does not exist [id: 0, uid: 0, owner_name: user2, name: _cargo-index]`,
+
+ // TestCommitMail/Delete/Not_activated
+ `:HTML() [E] Render failed: failed to render template: repo/editor/edit, error: template error: builtin(static):repo/editor/edit:13:13 : executing "repo/editor/edit" at <len .TreeNames>: error calling len: reflect: call of reflect.Value.Type on zero Value
+----------------------------------------------------------------------
+ {{$n := len .TreeNames}}
+ ^
+----------------------------------------------------------------------`,
+ // TestCommitMail/Delete/Not_belong_to_user
+ `:HTML() [E] Render failed: failed to render template: repo/editor/edit, error: template error: builtin(static):repo/editor/edit:13:13 : executing "repo/editor/edit" at <len .TreeNames>: error calling len: reflect: call of reflect.Value.Type on zero Value
+----------------------------------------------------------------------
+ {{$n := len .TreeNames}}
+ ^
+----------------------------------------------------------------------`,
+ // TestCommitMail/Apply_patch/Not_activated
+ `:HTML() [E] Render failed: failed to render template: repo/editor/edit, error: template error: builtin(static):repo/editor/edit:13:13 : executing "repo/editor/edit" at <len .TreeNames>: error calling len: reflect: call of reflect.Value.Type on zero Value
+----------------------------------------------------------------------
+ {{$n := len .TreeNames}}
+ ^
+----------------------------------------------------------------------`,
+ // TestCommitMail/Apply_patch/Not_belong_to_user
+ `:HTML() [E] Render failed: failed to render template: repo/editor/edit, error: template error: builtin(static):repo/editor/edit:13:13 : executing "repo/editor/edit" at <len .TreeNames>: error calling len: reflect: call of reflect.Value.Type on zero Value
+----------------------------------------------------------------------
+ {{$n := len .TreeNames}}
+ ^
+----------------------------------------------------------------------`,
+ // TestCommitMail/Cherry_pick/Not_activated
+ `:HTML() [E] Render failed: failed to render template: repo/editor/edit, error: template error: builtin(static):repo/editor/edit:13:13 : executing "repo/editor/edit" at <len .TreeNames>: error calling len: reflect: call of reflect.Value.Type on zero Value
+----------------------------------------------------------------------
+ {{$n := len .TreeNames}}
+ ^
+----------------------------------------------------------------------`,
+ // TestCommitMail/Cherry_pick/Not_belong_to_user
+ `:HTML() [E] Render failed: failed to render template: repo/editor/edit, error: template error: builtin(static):repo/editor/edit:13:13 : executing "repo/editor/edit" at <len .TreeNames>: error calling len: reflect: call of reflect.Value.Type on zero Value
+----------------------------------------------------------------------
+ {{$n := len .TreeNames}}
+ ^
+----------------------------------------------------------------------`,
+ // TestDangerZoneConfirmation/Convert_fork/Fail
+ `/gitea-repositories/user20/big_test_public_fork_7.git Error: no such file or directory`,
+ // TestGitSmartHTTP
+ `:sendFile() [E] request file path contains invalid path: objects/info/..\..\..\..\custom\conf\app.ini`,
+ // TestGit/HTTP/BranchProtectMerge
+ `:SSHLog() [E] ssh: Not allowed to push to protected branch protected. HookPreReceive(last) failed: internal API error response, status=403`,
+ // TestGit/HTTP/BranchProtectMerge
+ `:SSHLog() [E] ssh: Not allowed to push to protected branch protected. HookPreReceive(last) failed: internal API error response, status=403`,
+ // TestGit/HTTP/BranchProtectMerge
+ `:SSHLog() [E] ssh: branch protected is protected from force push. HookPreReceive(last) failed: internal API error response, status=403`,
+ // TestGit/HTTP/MergeFork/CreatePRAndMerge
+ `:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1099 name: user2:master]`, // sqlite
+ "s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 10000 name: user2:master]", // mysql
+ "s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1060 name: user2:master]", // pgsql
+ // TestGit/HTTP/BranchProtectMerge
+ `:func1() [E] PushToBaseRepo: PushRejected Error: exit status 1 - remote: error: cannot lock ref`,
+ // TestGit/SSH/BranchProtectMerge
+ `:func1() [E] PushToBaseRepo: PushRejected Error: exit status 1 - remote: error: cannot lock ref`,
+ // TestGit/SSH/LFS/PushCommit/Little
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/PushCommit/Little
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/PushCommit/Big
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/PushCommit/Big
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/Locks
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/Locks
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/Locks
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/Locks
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/LFS/Locks
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/PushParams/NoParams
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/PushParams/NoParams
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/PushParams/TitleOverride
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/PushParams/TitleOverride
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/PushParams/DescriptionOverride
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/PushParams/DescriptionOverride
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Force_push/Fails
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Force_push/Fails
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Force_push/Succeeds
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Force_push/Succeeds
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Force_push
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Force_push
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Branch_already_contains_commit
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull/Branch_already_contains_commit
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/CreateAgitFlowPull
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Not allowed to push to protected branch protected. HookPreReceive(last) failed: internal API error response, status=403`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: branch protected is protected from force push. HookPreReceive(last) failed: internal API error response, status=403`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/BranchProtectMerge
+ `:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
+ // TestGit/SSH/MergeFork/CreatePRAndMerge
+ `:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1102 name: user2:master]`, // sqlite
+ "s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 10003 name: user2:master]", // mysql
+ "s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1063 name: user2:master]", // pgsql
+ // TestGit/SSH/PushCreate
+ `:SSHLog() [E] ssh: Push to create is not enabled for users. ServCommand failed: internal API error response, status=403`,
+ // TestGit/SSH/PushCreate
+ `:SSHLog() [E] ssh: Cannot find repository: user2/repo-tmp-push-create-ssh. ServCommand failed: internal API error response, status=404`,
+ // TestGit/SSH/PushCreate
+ `:SSHLog() [E] ssh: Invalid repo name. Invalid repo name: invalid/repo-tmp-push-create-ssh`,
+ // TestIssueReaction
+ `:ChangeIssueReaction() [E] ChangeIssueReaction: '8ball' is not an allowed reaction`,
+ // TestIssuePinMove
+ `:IssuePinMove() [E] Issue does not belong to this repository`,
+ // TestLinksLogin
+ `:GetIssuesAllCommitStatus() [E] getAllCommitStatus: cant get commit statuses of pull [6]: object does not exist [id: refs/pull/2/head, rel_path: ]`,
+ // TestLinksLogin
+ `:GetIssuesAllCommitStatus() [E] getAllCommitStatus: cant get commit statuses of pull [6]: object does not exist [id: refs/pull/2/head, rel_path: ]`,
+ // TestLinksLogin
+ `:GetIssuesAllCommitStatus() [E] getAllCommitStatus: cant get commit statuses of pull [6]: object does not exist [id: refs/pull/2/head, rel_path: ]`,
+ // TestLinksLogin
+ `:GetIssuesAllCommitStatus() [E] Cannot open git repository <Repository 23:org17/big_test_public_4> for issue #1[20]. Error: no such file or directory`,
+ // TestMigrate
+ `] for OwnerID[2] failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
+ // TestMigrate
+ `:handler() [E] Run task failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
+ // TestMigrate
+ `] for OwnerID[2] failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
+ // TestMigrate
+ `:handler() [E] Run task failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
+ // TestMirrorPush
+ `:GetInfoRefs() [E] fork/exec /usr/bin/git: no such file or directory -`,
+
+ // TestOrgMembers
+ `:loadOrganizationOwners() [E] Organization does not have owner team: 25`,
+ // TestOrgMembers
+ `:loadOrganizationOwners() [E] Organization does not have owner team: 25`,
+ // TestOrgMembers
+ `:loadOrganizationOwners() [E] Organization does not have owner team: 25`,
+ // TestRecentlyPushed/unrelated_branches_are_not_shown
+ `:SyncRepoBranches() [E] OpenRepository[user30/repo50]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestRecentlyPushed/unrelated_branches_are_not_shown
+ `:handlerBranchSync() [E] syncRepoBranches [50] failed: no such file or directory`,
+ // TestRecentlyPushed/unrelated_branches_are_not_shown
+ `:SyncRepoBranches() [E] OpenRepository[user30/repo51]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestRecentlyPushed/unrelated_branches_are_not_shown
+ `:handlerBranchSync() [E] syncRepoBranches [51] failed: no such file or directory`,
+ // TestRecentlyPushed/unrelated_branches_are_not_shown
+ `:SyncRepoBranches() [E] OpenRepository[user2/scoped_label]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestRecentlyPushed/unrelated_branches_are_not_shown
+ `:handlerBranchSync() [E] syncRepoBranches [55] failed: no such file or directory`,
+ // TestCantMergeConflict
+ "]user1/repo1#1[base...conflict]> Unable to merge tracking into base: Merge Conflict Error: exit status 1: \nAuto-merging README.md\nCONFLICT (content): Merge conflict in README.md\nAutomatic merge failed; fix conflicts and then commit the result.",
+
+ // TestCantMergeUnrelated
+ `]user1/repo1#1[base...unrelated]> Unable to merge tracking into base: Merge UnrelatedHistories Error: exit status 128: fatal: refusing to merge unrelated histories`,
+ // TestCantFastForwardOnlyMergeDiverging
+ "]user1/repo1#1[master...diverging]> Unable to merge tracking into base: Merge DivergingFastForwardOnly Error: exit status 128: hint: Diverging branches can't be fast-forwarded, you need to either:\nhint: \nhint: \tgit merge --no-ff\nhint: \nhint: or:\nhint: \nhint: \tgit rebase\nhint: \nhint: Disable this message with \"git config advice.diverging false\"\nfatal: Not possible to fast-forward, aborting.",
+ // TestPullrequestReopen/Base_branch_deleted
+ `fatal: couldn't find remote ref base-branch`,
+ // TestPullrequestReopen/Head_branch_deleted
+ `]user2/reopen-base#1[base-branch...org26/reopen-head:head-branch]>]: branch does not exist [repo_id: 0 name: head-branch]`,
+ // TestDatabaseMissingABranch
+ `:SyncRepoBranches() [E] OpenRepository[user30/repo50]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestDatabaseMissingABranch
+ `:handlerBranchSync() [E] syncRepoBranches [50] failed: no such file or directory`,
+ // TestDatabaseMissingABranch
+ `:SyncRepoBranches() [E] OpenRepository[user30/repo51]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestDatabaseMissingABranch
+ `:handlerBranchSync() [E] syncRepoBranches [51] failed: no such file or directory`,
+ // TestDatabaseMissingABranch
+ `:SyncRepoBranches() [E] OpenRepository[user2/scoped_label]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestDatabaseMissingABranch
+ `:handlerBranchSync() [E] syncRepoBranches [55] failed: no such file or directory`,
+ // TestDatabaseMissingABranch
+ `:LoadBranches() [E] loadOneBranch() on repo #1, branch 'will-be-missing' failed: CountDivergingCommits: exit status 128 - fatal: bad revision 'master...refs/heads/will-be-missing'
+ - fatal: bad revision 'master...refs/heads/will-be-missing'`,
+ // TestDatabaseMissingABranch
+ `:SyncRepoBranches() [E] OpenRepository[user30/repo50]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestDatabaseMissingABranch
+ `:handlerBranchSync() [E] syncRepoBranches [50] failed: no such file or directory`,
+ // TestDatabaseMissingABranch
+ `:SyncRepoBranches() [E] OpenRepository[user30/repo51]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestDatabaseMissingABranch
+ `:handlerBranchSync() [E] syncRepoBranches [51] failed: no such file or directory`,
+ // TestDatabaseMissingABranch
+ `:SyncRepoBranches() [E] OpenRepository[user2/scoped_label]: %!w(*errors.errorString=&{no such file or directory})`,
+ // TestDatabaseMissingABranch
+ `:handlerBranchSync() [E] syncRepoBranches [55] failed: no such file or directory`,
+ // TestDatabaseMissingABranch
+ "LoadBranches() [E] loadOneBranch() on repo #1, branch 'will-be-missing' failed: CountDivergingCommits: exit status 128 - fatal: bad revision 'master...refs/heads/will-be-missing'\n - fatal: bad revision 'master...refs/heads/will-be-missing'",
+
+ // TestCreateNewTagProtected/Git
+ `:SSHLog() [E] ssh: Tag v-2 is protected. HookPreReceive(last) failed: internal API error response, status=403`,
+ // TestMarkDownReadmeImage
+ `:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: home-md-img-check]`,
+ // TestMarkDownReadmeImage
+ `:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: home-md-img-check]`,
+ // TestMarkDownReadmeImageSubfolder
+ `:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: sub-home-md-img-check]`,
+ // TestMarkDownReadmeImageSubfolder
+ `:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: sub-home-md-img-check]`,
+
+ // TestKeyOnlyOneType
+ `:ssh-key-test-repo-push is not authorized to write to user2/ssh-key-test-repo. ServCommand failed: internal API error response, status=401`,
+
+ // TestPullRebase
+ "/gitea-repositories/user2/repo1.git' does not appear to be a git repository\nfatal: Could not read from remote repository.\n\nPlease make sure you have the correct access rights\nand the repository exists.",
+
+ // TestPullRebaseMerge
+ "]user2/repo1#3[master...branch2]>]: branch does not exist [repo_id: 0 name: branch2]",
+
+ // TestAuthorizeNoClientID
+ `TrString() [E] Missing translation "form.ResponseType"`,
+
+ // TestWebhookForms
+ `TrString() [E] Missing translation "form.AuthorizationHeader"`,
+ `TrString() [E] Missing translation "form.Channel"`,
+ `TrString() [E] Missing translation "form.ContentType"`,
+ `TrString() [E] Missing translation "form.HTTPMethod"`,
+ `TrString() [E] Missing translation "form.PayloadURL"`,
+
+ // TestRenameInvalidUsername
+ `TrString() [E] Missing translation "form.Name"`,
}
func (w *testLoggerWriterCloser) recordError(msg string) {
- for _, s := range ignoredErrorMessageSuffixes {
- if strings.HasSuffix(msg, s) {
+ for _, s := range ignoredErrorMessage {
+ if strings.Contains(msg, s) {
return
}
}
@@ -128,6 +410,11 @@ func (w *testLoggerWriterCloser) recordError(msg string) {
err = w.errs[len(w.errs)-1]
}
+ if len(w.t) > 0 {
+ // format error message to easily add it to the ignore list
+ msg = fmt.Sprintf("// %s\n\t%q,", w.t[len(w.t)-1].Name(), msg)
+ }
+
err = errors.Join(err, errors.New(msg))
if len(w.errs) > 0 {
@@ -231,7 +518,9 @@ func PrintCurrentTest(t testing.TB, skip ...int) func() {
}
if err := WriterCloser.popT(); err != nil {
- t.Errorf("testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
+ // disable test failure for now (too flacky)
+ _, _ = fmt.Fprintf(os.Stdout, "testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
+ // t.Errorf("testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
}
}
}
diff --git a/options/locale/locale_bg.ini b/options/locale/locale_bg.ini
index 5b9bb0715e..f2aeb2a8b9 100644
--- a/options/locale/locale_bg.ini
+++ b/options/locale/locale_bg.ini
@@ -129,6 +129,12 @@ permission_write = Четене и ПиÑане
twofa_disable = Изключване на двуфакторното удоÑтоверÑване
twofa_enroll = Включване на двуфакторно удоÑтоверÑване
ssh_key_name_used = Вече ÑъщеÑтвува SSH ключ ÑÑŠÑ Ñъщото име във Ð²Ð°ÑˆÐ¸Ñ Ð°ÐºÐ°ÑƒÐ½Ñ‚.
+email_notifications.enable = Включване на извеÑтиÑта по ел. поща
+delete_prompt = Тази Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ñ‰Ðµ изтрие перманентно потребителÑÐºÐ¸Ñ Ð²Ð¸ акаунт. Това <strong>ÐЕ МОЖЕ</strong> да бъде отменено.
+email_notifications.disable = Изключване на извеÑтиÑта по ел. поща
+delete_account = Изтриване на акаунта ви
+confirm_delete_account = Потвърждаване на изтриването
+email_notifications.onmention = Ел. поща Ñамо при Ñпоменаване
[packages]
container.labels.value = СтойноÑÑ‚
@@ -269,6 +275,7 @@ filter.not_template = Ðе шаблони
filter.private = ЧаÑтни
filter.is_mirror = Огледални
filter.not_mirror = Ðе огледални
+copy_hash = Копиране на контролната Ñума
[repo]
issues.context.edit = Редактиране
@@ -366,7 +373,7 @@ issues.keyword_search_unavailable = Ð’ момента Ñ‚ÑŠÑ€Ñенето по к
repo_desc_helper = Въведете кратко опиÑание (опционално)
mirror_address = Клониране от URL
owner_helper = ÐÑкои организации може да не Ñе показват в падащото меню поради ограничение за макÑимален брой хранилища.
-new_repo_helper = Хранилище Ñъдържа вÑички файлове на проекта, включително хронологиÑта на ревизиите. Вече хоÑтвате хранилище другаде? <a href="%s">Мигрирайте хранилище.</a>
+new_repo_helper = Хранилището Ñъдържа вÑички файлове на проекта, включително хронологиÑта на ревизиите. Вече хоÑтвате хранилище другаде? <a href="%s">Мигрирайте хранилище.</a>
repo_name_helper = Добрите имена на хранилища използват кратки, запомнÑщи Ñе и уникални ключови думи.
migrated_from = Мигрирано от <a href="%[1]s">%[2]s</a>
visibility_description = Само притежателÑÑ‚ или учаÑтниците в организациÑта, ако имат права, ще могат да го видÑÑ‚.
@@ -667,10 +674,10 @@ milestones.close = ЗатварÑне
issues.label_templates.use = Използване на набор от етикети
issues.add_milestone_at = `добави това към етапа <b>%s</b> %s`
issues.add_label = добави етикета %s %s
-issues.add_labels = добави етикетите %s %s
+issues.add_labels = добави етикети %s %s
issues.remove_label = премахна етикета %s %s
issues.remove_labels = премахна етикетите %s %s
-issues.add_remove_labels = добави етикетите %s и премахна %s %s
+issues.add_remove_labels = добави етикети %s и премахна %s %s
issues.add_project_at = `добави това към проекта <b>%s</b> %s`
issues.remove_project_at = `премахна това от проекта <b>%s</b> %s`
issues.remove_milestone_at = `премахна това от етапа <b>%s</b> %s`
@@ -702,7 +709,7 @@ more_operations = Още операции
download_archive = ИзтеглÑне на хранилището
branch = Клон
tree = Дърво
-branches = Клони
+branches = Клонове
tags = Маркери
tag = Маркер
filter_branch_and_tag = Филтр. на клон или маркер
@@ -759,7 +766,7 @@ pulls.merged_by = от <a href="%[2]s">%[3]s</a> бе ÑлÑта %[1]s
pulls.merged_by_fake = от %[2]s бе ÑлÑта %[1]s
issues.label_deletion = Изтриване на етикета
issues.label_modify = Редактиране на етикета
-issues.due_date_added = добави ÐºÑ€Ð°Ð¹Ð½Ð¸Ñ Ñрок %s %s
+issues.due_date_added = добави краен Ñрок %s %s
issues.due_date_remove = премахна ÐºÑ€Ð°Ð¹Ð½Ð¸Ñ Ñрок %s %s
release.new_release = Ðово издание
release.tag_helper_existing = СъщеÑтвуващ маркер.
@@ -822,7 +829,7 @@ editor.fail_to_update_file = ÐеуÑпешно обновÑване/ÑъздаÐ
editor.add_subdir = ДобавÑне на директориÑ…
commits.commits = ПодаваниÑ
commits.find = ТърÑене
-commits.search_all = Ð’Ñички клони
+commits.search_all = Ð’Ñички клонове
commits.search = ПотърÑете подаваниÑ…
commit.operations = Операции
issues.deleted_milestone = `(изтрит)`
@@ -848,7 +855,7 @@ release.edit_release = ОбновÑване на изданието
diff.committed_by = подадено от
release.downloads = ИзтеглÑниÑ
issues.sign_in_require_desc = <a href="%s">Влезте</a> за да Ñе приÑъедините към това обÑъждане.
-activity.git_stats_push_to_all_branches = към вÑички клони.
+activity.git_stats_push_to_all_branches = към вÑички клонове.
release.deletion_tag_success = Маркерът е изтрит.
release.cancel = Отказ
release.deletion = Изтриване на изданието
@@ -928,9 +935,9 @@ settings.web_hook_name_discord = Discord
settings.web_hook_name_telegram = Telegram
settings.web_hook_name_matrix = Matrix
settings.web_hook_name_gogs = Gogs
-settings.web_hook_name_feishu_or_larksuite = Feishu / Lark Suite
-settings.web_hook_name_feishu = Feishu
-settings.web_hook_name_larksuite = Lark Suite
+settings.web_hook_name_feishu = Feishu / Lark Suite
+settings.web_hook_name_feishu_only = Feishu
+settings.web_hook_name_larksuite_only = Lark Suite
settings.web_hook_name_wechatwork = WeCom (Wechat Work)
settings.web_hook_name_packagist = Packagist
diff.file_byte_size = Размер
@@ -959,6 +966,57 @@ search.results = Резултати от Ñ‚ÑŠÑ€Ñенето на "%s" в <a href
object_format = Формат на обектите
release.releases_for = Ð˜Ð·Ð´Ð°Ð½Ð¸Ñ Ð·Ð° %s
release.tags_for = Маркери за %s
+pulls.cmd_instruction_hint = `Вижте <a class="show-instruction">инÑтрукциите за ÐºÐ¾Ð¼Ð°Ð½Ð´Ð½Ð¸Ñ Ñ€ÐµÐ´</a>.`
+pulls.showing_only_single_commit = Показани Ñа Ñамо промените в подаване %[1]s
+issues.lock_no_reason = заключи и ограничи обÑъждането до Ñътрудници %s
+pulls.expand_files = Разгъване на вÑички файлове
+pulls.title_desc_few = иÑка да Ñлее %[1]d Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ Ð¾Ñ‚ <code>%[2]s</code> в <code id="branch_target">%[3]s</code>
+issues.content_history.deleted = изтрито
+activity.git_stats_exclude_merges = С изключение на ÑливаниÑта,
+activity.navbar.pulse = ПоÑледна дейноÑÑ‚
+activity.no_git_activity = Ðе е имало никаква дейноÑÑ‚ Ñ Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ Ð¿Ñ€ÐµÐ· този период.
+pulls.merged_title_desc_few = ÑÐ»Ñ %[1]d Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ Ð¾Ñ‚ <code>%[2]s</code> в <code>%[3]s</code> %[4]s
+diff.stats_desc_file = %d промени: %d добавÑÐ½Ð¸Ñ Ð¸ %d изтриваниÑ
+issues.content_history.created = Ñъздадено
+pulls.status_checks_success = Ð’Ñички проверки бÑха уÑпешни
+activity.git_stats_pushed_n = Ñа изтлаÑкали
+pulls.select_commit_hold_shift_for_range = Изберете подаване. Задръжте shift + click, за да изберете обхвата
+activity.git_stats_addition_1 = %d добавÑне
+activity.git_stats_on_default_branch = Ð’ %s,
+activity.git_stats_files_changed_1 = е променен
+activity.git_stats_files_changed_n = Ñа променени
+activity.git_stats_additions = и е имало
+pulls.collapse_files = Свиване на вÑички файлове
+pulls.show_all_commits = Показване на вÑички подаваниÑ
+diff.whitespace_button = Празни знаци
+issues.content_history.edited = редактирано
+pulls.title_desc_one = иÑка да Ñлее %[1]d подаване от <code>%[2]s</code> в <code id="branch_target">%[3]s</code>
+pulls.showing_specified_commit_range = Показани Ñа Ñамо промените между %[1]s..%[2]s
+pulls.merged_title_desc_one = ÑÐ»Ñ %[1]d подаване от <code>%[2]s</code> в <code>%[3]s</code> %[4]s
+pulls.no_merge_access = Ðе Ñте упълномощени за Ñливане на тази заÑвка за Ñливане.
+activity.navbar.code_frequency = ЧеÑтота на кода
+activity.git_stats_pushed_1 = е изтлаÑкал
+activity.git_stats_push_to_branch = към %s и
+contributors.contribution_type.commits = ПодаваниÑ
+stars = Звезди
+n_commit_few = %s подаваниÑ
+n_branch_one = %s клон
+n_branch_few = %s клона
+n_tag_one = %s маркер
+n_tag_few = %s маркера
+commit_graph = Граф Ñ Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ
+commits.renamed_from = Преименувано от %s
+commits.view_path = Преглед на този момент в иÑториÑта
+commits.search_branch = Този клон
+n_commit_one = %s подаване
+release.ahead.commits = <strong>%d</strong> подаваниÑ
+release.stable = Стабилно
+commits.gpg_key_id = ID на GPG ключ
+diff.options_button = Опции за разликите
+activity.title.unresolved_conv_1 = %d нерешено обÑъждане
+activity.title.unresolved_conv_n = %d нерешени обÑъжданиÑ
+issues.comment_pull_merged_at = ÑÐ»Ñ Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ %[1]s в %[2]s %[3]s
+issues.comment_manually_pull_merged_at = ръчно ÑÐ»Ñ Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ %[1]s в %[2]s %[3]s
[modal]
confirm = Потвърждаване
@@ -1090,6 +1148,7 @@ sqlite_helper = Път на файла за SQLite3 базата данни.<br>
err_empty_admin_email = ÐдминиÑтраторÑкиÑÑ‚ Ð°Ð´Ñ€ÐµÑ Ð½Ð° ел. поща не може да бъде празен.
password_algorithm = Ðлгоритъм за хеш. на паролите
default_keep_email_private = Скриване на адреÑите на ел. поща по подразбиране
+invalid_password_algorithm = Ðевалиден алгоритъм за хеш. на паролите
[filter]
string.asc = Р- Я
@@ -1135,6 +1194,7 @@ change_avatar = Променете профилната Ñи Ñнимка…
email_visibility.limited = ВашиÑÑ‚ Ð°Ð´Ñ€ÐµÑ Ð½Ð° ел. поща е видим за вÑички удоÑтоверени потребители
disabled_public_activity = Този потребител е изключил публичната видимоÑÑ‚ на дейноÑтта.
email_visibility.private = ВашиÑÑ‚ Ð°Ð´Ñ€ÐµÑ Ð½Ð° ел. поща е видим Ñамо за Ð²Ð°Ñ Ð¸ админиÑтраторите
+show_on_map = Показване на това мÑÑто на картата
[home]
filter = Други филтри
@@ -1288,6 +1348,7 @@ publish_release = `публикува издание <a href="%[2]s"> "%[4]s" </
push_tag = изтлаÑка маркер <a href="%[2]s">%[3]s</a> към <a href="%[1]s">%[4]s</a>
approve_pull_request = `одобри <a href="%[1]s">%[3]s#%[2]s</a>`
reject_pull_request = `предложи промени за <a href="%[1]s">%[3]s#%[2]s</a>`
+compare_branch = СравнÑване
[auth]
tab_openid = OpenID
@@ -1408,6 +1469,20 @@ component_loading_failed = ÐеуÑпешно зареждане на %s
contributors.what = приноÑи
recent_commits.what = Ñкорошни подаваниÑ
component_loading = Зареждане на %s...
+component_loading_info = Това може да отнеме извеÑтно време…
[projects]
type-1.display_name = Индивидуален проект
+
+
+[search]
+no_results = ÐÑма намерени ÑъответÑтващи резултати.
+team_kind = ТърÑене на екипи...
+repo_kind = ТърÑене на хранилища...
+org_kind = ТърÑене на организации...
+user_kind = ТърÑене на потребители...
+code_kind = ТърÑене на код...
+commit_kind = ТърÑене на подаваниÑ...
+project_kind = ТърÑене на проекти...
+package_kind = ТърÑене на пакети...
+search = ТърÑене... \ No newline at end of file
diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini
index 3747ae28c9..092c25aa80 100644
--- a/options/locale/locale_cs-CZ.ini
+++ b/options/locale/locale_cs-CZ.ini
@@ -157,6 +157,8 @@ filter.is_archived = Archivováno
filter.not_mirror = Není zrcadleno
filter.not_archived = Není archivováno
filter.clear = Vymazat filtr
+more_items = Další položky
+invalid_data = Neplatná data: %v
[aria]
navbar=NavigaÄní liÅ¡ta
@@ -2306,9 +2308,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu / Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Uživatelské jméno pro Packagist
@@ -2705,6 +2707,17 @@ pulls.title_desc_one = žádá o slouÄení %[1]d commitu z <code>%[2]s</code> d
pulls.merged_title_desc_one = slouÄil %[1]d commit z <code>%[2]s</code> do <code>%[3]s</code> %[4]s
open_with_editor = Otevřít pomocí %s
commits.search_branch = Tato větev
+editor.commit_id_not_matching = ID commitu se neshoduje s ID získaném pÅ™i zaÄátku úprav. ProveÄte commit do nové vÄ›tve a poté zmÄ›ny sluÄte.
+pulls.ready_for_review = Připraveni na posouzení?
+settings.rename_branch_failed_protected = Nepodařilo se přejmenovat větev %s, jelikož se jedná o chráněnou větev.
+editor.push_out_of_date = Push je nejspíše zastaralý.
+stars = Oblíbení
+n_commit_one = %s commit
+n_commit_few = %s commitů
+n_branch_one = %s větev
+n_tag_one = %s znaÄka
+n_tag_few = %s znaÄek
+n_branch_few = %s větví
[graphs]
component_loading_info = Tohle může chvíli trvat…
@@ -3391,6 +3404,7 @@ auths.tips.gmail_settings = Nastavení služby Gmail:
config_summary = Souhrn
config.open_with_editor_app_help = Editory v nabídce „Otevřít pomocí“ v nabídce klonování. Ponechte prázdné pro použití výchozího editoru (zobrazíte jej rozšířením).
config_settings = Nastavení
+auths.tip.gitlab_new = Zaregistrujte si novou aplikaci na https://gitlab.com/-/profile/applications
[action]
create_repo=vytvořil/a repozitář <a href="%s">%s</a>
@@ -3745,6 +3759,7 @@ runners = Runnery
runs.pushed_by = pushnuto uživatelem
need_approval_desc = PotÅ™ebovat schválení pro spouÅ¡tÄ›ní workflowů pro žádosti o slouÄení forků.
runners.runner_manage_panel = Správa runnerů
+runs.no_job_without_needs = Workflow musí obsahovat alespoň jednu práci bez závislostí.
[projects]
type-1.display_name=Samostatný projekt
diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini
index f5aafdf5c9..2cab9733ac 100644
--- a/options/locale/locale_de-DE.ini
+++ b/options/locale/locale_de-DE.ini
@@ -155,6 +155,8 @@ filter.is_template = Vorlage
filter.not_template = Keine Vorlage
filter.public = Öffentlich
filter.private = Privat
+more_items = Mehr Einträge
+invalid_data = Ungültige Daten: %v
[aria]
navbar=Navigationsleiste
@@ -979,10 +981,10 @@ user_unblock_success = Die Blockierung dieses Benutzers wurde erfolgreich zurüc
blocked_users = Blockierte Benutzer
blocked_since = Blockiert seit %s
change_password = Passwort ändern
-hints = Tipps
+hints = Hinweise
additional_repo_units_hint = Zur Aktivierung zusätzlicher Repository-Einheiten ermutigen
-update_hints = Tipps aktualisieren
-update_hints_success = Tipps wurden aktualisiert.
+update_hints = Hinweise aktualisieren
+update_hints_success = Hinweise wurden aktualisiert.
additional_repo_units_hint_description = Einen „Mehr Einheiten hinzufügen …“-Button für Repositorys, welche nicht alle verfügbaren Einheiten aktiviert haben, anzeigen.
[repo]
@@ -2302,9 +2304,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu / Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Benutzername für Packagist
@@ -2690,6 +2692,16 @@ pulls.title_desc_one = möchte %[1]d Commit von <code>%[2]s</code> nach <code id
open_with_editor = Öffnen mit %s
commits.search_branch = Dieser Branch
pulls.ready_for_review = Bereit zum Review?
+settings.rename_branch_failed_protected = Branch %s kann nicht umbenannt werden, weil er ein geschützter Branch ist.
+editor.commit_id_not_matching = Die Commit-ID passt nicht zur ID zum Zeitpunkt, als du mit der Bearbeitung angefangen hast. Committe in einen Patch-Branch, dann mach einen Merge.
+editor.push_out_of_date = Der Push scheint veraltet zu sein.
+n_commit_few = %s Commits
+n_branch_one = %s Branch
+n_branch_few = %s Branches
+n_tag_one = %s Tag
+n_tag_few = %s Tags
+stars = Favorisierungen
+n_commit_one = %s Commit
[graphs]
@@ -3358,6 +3370,7 @@ auths.tips.gmail_settings = Gmail-Einstellungen:
config_settings = Einstellungen
config.open_with_editor_app_help = Die „Öffnen mit“-Editoren für das Klonmenü. Falls es leer gelassen wird, wird der Standardwert benutzt. Erweitern, um den Standardwert zu sehen.
config_summary = Zusammenfassung
+auths.tip.gitlab_new = Registriere eine neue Anwendung auf https://gitlab.com/-/profile/applications
[action]
@@ -3711,6 +3724,7 @@ runs.no_workflows.documentation = Für weitere Informationen über Forgejo Actio
runs.empty_commit_message = (leere Commit-Nachricht)
variables.id_not_exist = Variable mit ID %d existiert nicht.
runs.workflow = Workflow
+runs.no_job_without_needs = Der Workflow muss mindestens einen Job ohne Abhängigkeiten enthalten.
[projects]
type-1.display_name=Individuelles Projekt
@@ -3757,4 +3771,5 @@ commit_kind = Commits suchen …
runner_kind = Runners suchen …
no_results = Keine passenden Ergebnisse gefunden.
code_search_unavailable = Die Code-Suche ist momentan nicht verfügbar. Bitte kontaktiere den Webseitenadministrator.
-keyword_search_unavailable = Suche nach Schlüsselwörtern ist momentan nicht unterstüzt. Bitte kontaktiere den Webseitenadministrator. \ No newline at end of file
+keyword_search_unavailable = Suche nach Schlüsselwörtern ist momentan nicht unterstüzt. Bitte kontaktiere den Webseitenadministrator.
+code_search_by_git_grep = Die derzeitigen Codesuchergebnisse werden durch „git grep“ bereitgestellt. Es könnten bessere Ergebnisse erzielt werden, wenn der Administrator die Repository-Indizierung aktiviert. \ No newline at end of file
diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini
index 36b7518c62..b9d2881d8a 100644
--- a/options/locale/locale_el-GR.ini
+++ b/options/locale/locale_el-GR.ini
@@ -2293,9 +2293,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu / Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Όνομα χÏήστη Packagist
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 5042e5467e..fbe67c28b8 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -146,15 +146,15 @@ name = Name
value = Value
filter = Filter
-filter.clear = Clear Filter
+filter.clear = Clear filters
filter.is_archived = Archived
-filter.not_archived = Not Archived
+filter.not_archived = Not archived
filter.is_fork = Forked
-filter.not_fork = Not Forked
+filter.not_fork = Not forked
filter.is_mirror = Mirrored
-filter.not_mirror = Not Mirrored
+filter.not_mirror = Not mirrored
filter.is_template = Template
-filter.not_template = Not Template
+filter.not_template = Not template
filter.public = Public
filter.private = Private
@@ -222,7 +222,7 @@ missing_csrf = Bad Request: no CSRF token present
invalid_csrf = Bad Request: invalid CSRF token
not_found = The target couldn't be found.
network_error = Network error
-server_internal = Internal Server Error
+server_internal = Internal server error
[startpage]
app_desc = A painless, self-hosted Git service
@@ -762,7 +762,6 @@ password_incorrect = The current password is incorrect.
change_password_success = Your password has been updated. Sign in using your new password from now on.
password_change_disabled = Non-local users cannot update their password through the Forgejo web interface.
-emails = Email addresses
manage_emails = Manage email addresses
manage_themes = Select default theme
manage_openid = Manage OpenID addresses
@@ -893,9 +892,9 @@ repo_and_org_access = Repository and Organization Access
permissions_public_only = Public only
permissions_access_all = All (public, private, and limited)
select_permissions = Select permissions
-permission_no_access = No Access
+permission_no_access = No access
permission_read = Read
-permission_write = Read and Write
+permission_write = Read and write
access_token_desc = Selected token permissions limit authorization only to the corresponding <a %s>API</a> routes. Read the <a %s>documentation</a> for more information.
at_least_one_permission = You must select at least one permission to create a token
permissions_list = Permissions:
@@ -915,8 +914,8 @@ oauth2_confidential_client = Confidential client. Select for apps that keep the
oauth2_redirect_uris = Redirect URIs. Please use a new line for every URI.
save_application = Save
oauth2_client_id = Client ID
-oauth2_client_secret = Client Secret
-oauth2_regenerate_secret = Regenerate Secret
+oauth2_client_secret = Client secret
+oauth2_regenerate_secret = Regenerate secret
oauth2_regenerate_secret_hint = Lost your secret?
oauth2_client_secret_hint = The secret will not be shown again after you leave or refresh this page. Please ensure that you have saved it.
oauth2_application_edit = Edit
@@ -927,7 +926,7 @@ oauth2_application_locked = Forgejo pre-registers some OAuth2 applications on st
authorized_oauth2_applications = Authorized OAuth2 applications
authorized_oauth2_applications_description = You have granted access to your personal Forgejo account to these third party applications. Please revoke access for applications that are no longer in use.
revoke_key = Revoke
-revoke_oauth2_grant = Revoke Access
+revoke_oauth2_grant = Revoke access
revoke_oauth2_grant_description = Revoking access for this third party application will prevent this application from accessing your data. Are you sure?
revoke_oauth2_grant_success = Access revoked successfully.
@@ -1143,7 +1142,6 @@ form.name_pattern_not_allowed = The pattern "%s" is not allowed in a repository
need_auth = Authorization
migrate_options = Migration options
-migrate_service = Migration service
migrate_options_mirror_helper = This repository will be a mirror
migrate_options_lfs = Migrate LFS files
migrate_options_lfs_endpoint.label = LFS endpoint
@@ -1254,11 +1252,11 @@ n_tag_few=%s tags
released_this = released this
file.title = %s at %s
file_raw = Raw
-file_follow = Follow Symlink
+file_follow = Follow symlink
file_history = History
-file_view_source = View Source
-file_view_rendered = View Rendered
-file_view_raw = View Raw
+file_view_source = View source
+file_view_rendered = View rendered
+file_view_raw = View raw
file_permalink = Permalink
file_too_large = The file is too large to be shown.
invisible_runes_header = `This file contains invisible Unicode characters`
@@ -1313,8 +1311,8 @@ editor.name_your_file = Name your file…
editor.filename_help = Add a directory by typing its name followed by a slash ("/"). Remove a directory by typing backspace at the beginning of the input field.
editor.or = or
editor.cancel_lower = Cancel
-editor.commit_signed_changes = Commit Signed Changes
-editor.commit_changes = Commit Changes
+editor.commit_signed_changes = Commit signed changes
+editor.commit_changes = Commit changes
editor.add_tmpl = Add "<filename>"
editor.add = Add %s
editor.update = Update %s
@@ -1342,9 +1340,9 @@ editor.file_is_a_symlink = `"%s" is a symbolic link. Symbolic links cannot be ed
editor.filename_is_a_directory = Filename "%s" is already used as a directory name in this repository.
editor.file_editing_no_longer_exists = The file being edited, "%s", no longer exists in this repository.
editor.file_deleting_no_longer_exists = The file being deleted, "%s", no longer exists in this repository.
-editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit Changes again</strong> to overwrite them.
+editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit changes again</strong> to overwrite them.
editor.file_already_exists = A file named "%s" already exists in this repository.
-editor.commit_id_not_matching = The Commit ID does not match the ID when you began editing. Commit into a patch branch and then merge.
+editor.commit_id_not_matching = The commit ID does not match the one you was editing. Commit to a new branch and then merge.
editor.push_out_of_date = The push appears to be out of date.
editor.commit_empty_file_header = Commit an empty file
editor.commit_empty_file_text = The file you're about to commit is empty. Proceed?
@@ -1419,29 +1417,26 @@ projects.edit_subheader = Projects organize issues and track progress.
projects.modify = Edit project
projects.edit_success = Project "%s" has been updated.
projects.type.none = None
-projects.type.basic_kanban = Basic Kanban
-projects.type.bug_triage = Bug Triage
+projects.type.basic_kanban = Basic kanban
+projects.type.bug_triage = Bug triage
projects.template.desc = Template
projects.template.desc_helper = Select a project template to get started
-projects.type.uncategorized = Uncategorized
-projects.column.edit = Edit Column
+projects.column.edit = Edit column
projects.column.edit_title = Name
projects.column.new_title = Name
-projects.column.new_submit = Create Column
-projects.column.new = New Column
-projects.column.set_default = Set Default
+projects.column.new_submit = Create column
+projects.column.new = New column
+projects.column.set_default = Set default
projects.column.set_default_desc = Set this column as default for uncategorized issues and pulls
-projects.column.unset_default = Unset Default
-projects.column.unset_default_desc = Unset this column as default
-projects.column.delete = Delete Column
-projects.column.deletion_desc = Deleting a project column moves all related issues to "Uncategorized". Continue?
+projects.column.delete = Delete column
+projects.column.deletion_desc = Deleting a project column moves all related issues to the default column. Continue?
projects.column.color = Color
projects.open = Open
projects.close = Close
projects.column.assigned_to = Assigned to
-projects.card_type.desc = Card Previews
-projects.card_type.images_and_text = Images and Text
-projects.card_type.text_only = Text Only
+projects.card_type.desc = Card previews
+projects.card_type.images_and_text = Images and text
+projects.card_type.text_only = Text only
issues.desc = Organize bug reports, tasks and milestones.
issues.filter_assignees = Filter Assignee
@@ -1452,7 +1447,7 @@ issues.filter_reviewers = Filter Reviewer
issues.new = New issue
issues.new.title_empty = Title cannot be empty
issues.new.labels = Labels
-issues.new.no_label = No label
+issues.new.no_label = No labels
issues.new.clear_labels = Clear labels
issues.new.projects = Projects
issues.new.clear_projects = Clear projects
@@ -1609,7 +1604,7 @@ issues.re_request_review=Re-request review
issues.is_stale = There have been changes to this PR since this review
issues.remove_request_review=Remove review request
issues.remove_request_review_block=Can't remove review request
-issues.dismiss_review = Dismiss Review
+issues.dismiss_review = Dismiss review
issues.dismiss_review_warning = Are you sure you want to dismiss this review?
issues.sign_in_require_desc = <a href="%s">Sign in</a> to join this conversation.
issues.edit = Edit
@@ -1983,7 +1978,7 @@ signing.wont_sign.commitssigned = The merge will not be signed as all the associ
signing.wont_sign.approved = The merge will not be signed as the PR is not approved.
signing.wont_sign.not_signed_in = You are not signed in.
-ext_wiki = Access to External Wiki
+ext_wiki = Access to external Wiki
ext_wiki.desc = Link to an external wiki.
wiki = Wiki
@@ -2376,9 +2371,9 @@ settings.web_hook_name_dingtalk = DingTalk
settings.web_hook_name_telegram = Telegram
settings.web_hook_name_matrix = Matrix
settings.web_hook_name_msteams = Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite = Feishu / Lark Suite
-settings.web_hook_name_feishu = Feishu
-settings.web_hook_name_larksuite = Lark Suite
+settings.web_hook_name_feishu = Feishu / Lark Suite
+settings.web_hook_name_feishu_only = Feishu
+settings.web_hook_name_larksuite_only = Lark Suite
settings.web_hook_name_wechatwork = WeCom (Wechat Work)
settings.web_hook_name_packagist = Packagist
settings.packagist_username = Packagist username
@@ -3151,7 +3146,7 @@ auths.tip.google_plus = Obtain OAuth2 client credentials from the Google API con
auths.tip.openid_connect = Use the OpenID Connect Discovery URL (<server>/.well-known/openid-configuration) to specify the endpoints
auths.tip.twitter = Go to https://dev.twitter.com/apps, create an application and ensure that the “Allow this application to be used to Sign in with Twitter†option is enabled
auths.tip.discord = Register a new application on https://discordapp.com/developers/applications/me
-auths.tip.gitea = Register a new OAuth2 application. Guide can be found at https://docs.gitea.com/development/oauth2-provider
+auths.tip.gitea = Register a new OAuth2 application. Guide can be found at https://forgejo.org/docs/latest/user/oauth2-provider
auths.tip.yandex = Create a new application at https://oauth.yandex.com/client/new. Select following permissions from the "Yandex.Passport API" section: "Access to email address", "Access to user avatar" and "Access to username, first name and surname, gender"
auths.tip.mastodon = Input a custom instance URL for the mastodon instance you want to authenticate with (or use the default one)
auths.edit = Edit authentication source
@@ -3186,7 +3181,7 @@ config.repo_root_path = Repository root path
config.lfs_root_path = LFS root path
config.log_file_root_path = Log path
config.script_type = Script type
-config.reverse_auth_user = Reverse authentication user
+config.reverse_auth_user = Reverse proxy authentication user
config.ssh_config = SSH configuration
config.ssh_enabled = Enabled
@@ -3733,3 +3728,8 @@ normal_file = Normal file
executable_file = Executable file
symbolic_link = Symbolic link
submodule = Submodule
+
+[markup]
+filepreview.line = Line %[1]d in %[2]s
+filepreview.lines = Lines %[1]d to %[2]d in %[3]s
+filepreview.truncated = Preview has been truncated
diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini
index 3dbca87381..a2794ccd3d 100644
--- a/options/locale/locale_es-ES.ini
+++ b/options/locale/locale_es-ES.ini
@@ -2267,9 +2267,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu / Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nombre de usuario Packagist
diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini
index c6c64ad6ce..31dca19f33 100644
--- a/options/locale/locale_fi-FI.ini
+++ b/options/locale/locale_fi-FI.ini
@@ -1184,8 +1184,8 @@ settings.web_hook_name_discord=Discord
settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_packagist=Packagist
settings.deploy_keys=Julkaisuavaimet
settings.add_deploy_key=Lisää julkaisuavain
diff --git a/options/locale/locale_fil.ini b/options/locale/locale_fil.ini
index b8c90857ee..a6f7b318eb 100644
--- a/options/locale/locale_fil.ini
+++ b/options/locale/locale_fil.ini
@@ -7,7 +7,7 @@ language = Wika
mirrors = Mga Mirror
forks = Mga Fork
activities = Mga Aktibidad
-pull_requests = Mga Pull Request
+pull_requests = Mga pull pequest
issues = Mga Isyu
milestones = Mga Milestone
ok = OK
@@ -87,8 +87,8 @@ new_fork = Bagong repository fork
new_org = Bagong organisasyon
new_project = Bagong proyekto
new_project_column = Bagong column
-admin_panel = Pangangasiwa ng Site
-account_settings = Mga Setting ng Account
+admin_panel = Pangangasiwa ng site
+account_settings = Mga setting ng Account
settings = Mga Setting
your_profile = Profile
your_starred = Naka-star
@@ -138,6 +138,8 @@ loading = Naglo-load…
confirm_delete_selected = Kumpirmahin na burahin ang lahat ng piniling item?
home = Panimula
dashboard = Dashboard
+more_items = Higit pang mga item
+invalid_data = Hindi wastong data: %v
[home]
search_repos = Maghanap ng Repository…
@@ -229,7 +231,7 @@ err_empty_admin_email = Hindi maaring walang laman ang administrator email.
err_admin_name_is_reserved = Hindi angkop ang Administrator Username, naka-reserve ang username
err_admin_name_is_invalid = Hindi angkop ang Administrator Username
general_title = Mga General Setting
-app_name = Pangalan ng Instansya
+app_name = Pamagat ng instansya
app_name_helper = Maari mong ilagay ang pangalan ng iyong kompanya dito.
repo_path_helper = Ang mga remote Git repository ay mase-save sa directory na ito.
repo_path = Root path ng Repository
@@ -288,7 +290,7 @@ invalid_db_setting = Hindi angkop ang mga database setting: %v
invalid_db_table = Hindi angkop ang database table na "%s": %v
invalid_repo_path = Hindi angkop ang repository root path: %v
invalid_app_data_path = Hindi angkop ang app data path: %v
-run_user_not_match = Ang "tumakbo bilang" na username ay hindi ang kasulukuyang username: %s -> %s
+run_user_not_match = Ang "user na tatakbo bilang" na username ay hindi ang kasulukuyang username: %s -> %s
internal_token_failed = Nabigong maka-generate ng internal token: %v
secret_key_failed = Nabigong maka-generate ng secret key: %v
save_config_failed = Nabigong i-save ang configuration: %v
@@ -921,6 +923,11 @@ oauth2_application_create_description = Ang mga OAuth2 application ay pinapayaga
oauth2_application_locked = Ang Forgejo ay pini-pre register ang ibang mga OAuth2 application sa startup kapag naka-enable sa config. Para iwasan ang hindi inaasahang gawain, hindi ito maaring i-edit o tanggalin. Mangyaring sumangguni sa dokumentasyon ng OAuth2 para sa karagdagang impormasyon.
remove_account_link_desc = Ang pagtanggal ng naka-link na account ay babawiin ang pag-access nito sa iyong Forgejo account. Magpatuloy?
visibility.public_tooltip = Makikita ng lahat
+hints = Mga Pahiwatig
+additional_repo_units_hint_description = Mag-display ng "Magdagdag pa ng mga unit..." na button para sa mga repository na hindi naka-enable ang lahat ng mga available na unit.
+additional_repo_units_hint = Hikayatin ang pag-enable ng karagdagang mga repository unit
+update_hints = I-update ang mga pahiwatig
+update_hints_success = Na-update na ang mga pahiwatig.
[repo]
template_description = Ang mga template repository ay pinapayagan ang mga gumagamit na mag-generate ng mga bagong repository na may magkatulad na istraktura ng direktoryo, mga file, at opsyonal na mga setting.
@@ -949,4 +956,104 @@ fork_repo = I-fork ang repository
fork_from = I-fork mula sa
already_forked = Na-fork mo na ang %s
fork_to_different_account = Mag-fork sa ibang account
-fork_visibility_helper = Ang visibility ng isang naka-fork na repository ay hindi maaring baguhin. \ No newline at end of file
+fork_visibility_helper = Ang visibility ng isang naka-fork na repository ay hindi maaring baguhin.
+open_with_editor = Buksan gamit ang %s
+download_bundle = I-download ang BUNDLE
+repo_gitignore_helper_desc = Piliin kung anong mga file na hindi susubaybayin sa listahan ng mga template para sa mga karaniwang wika. Ang mga tipikal na artifact na ginagawa ng mga build tool ng wika ay kasama sa .gitignore ng default.
+adopt_preexisting = Mag-adopt ng mga umiiral na file
+repo_gitignore_helper = Pumili ng mga .gitignore template.
+readme_helper_desc = Ito ang lugar kung saan makakasulat ka ng kumpletong deskripsyon para sa iyong proyekto.
+trust_model_helper_collaborator_committer = Katulong+Committer: I-trust ang mga signature batay sa mga katulong na tumutugma sa committer
+mirror_interval = Interval ng mirror (ang mga wastong unit ng oras ay "h", "m", "s"). 0 para i-disable ang periodic sync. (Pinakamababang interval: %s)
+transfer.reject_desc = Kanselahin ang pag-transfer mula sa "%s"
+mirror_lfs_endpoint_desc = Ang sync ay susubukang gamitin ang clone url upang <a target="_blank" rel="noopener noreferrer" href="%s">matukoy ang LFS server</a>. Maari ka rin tumukoy ng isang custom na endpoint kapag ang repository LFS data ay nilalagay sa ibang lugar.
+adopt_search = Ilagay ang username para maghanap ng mga unadopted repository... (iwanang walang laman para hanapin lahat)
+object_format = Format ng object
+readme_helper = Pumili ng README file template.
+default_branch_helper = Ang default branch ay ang base branch para sa mga pull request at mga commit ng code.
+mirror_interval_invalid = Hindi wasto ang mirror interval.
+mirror_sync = na-sync
+mirror_sync_on_commit = I-sync kapag na-push ang mga commit
+mirror_address = Mag-clone mula sa URL
+mirror_address_desc = Maglagay ng anumang mga kinakailangang kredensyal sa Awtorisasyon na seksyon.
+desc.archived = Naka-archive
+desc.sha256 = SHA256
+template.items = Mga template item
+template.git_content = Nilalaman ng Git (Default na branch)
+reactions_more = at %d pa
+unit_disabled = Na-disable ng tagapangasiwa ng site ang itong seksyon ng repository.
+create_repo = Gumawa ng Repository
+generate_from = I-generate mula sa
+repo_desc = Deskripsyon
+fork_branch = Branch na mako-clone sa fork
+all_branches = Lahat ng mga branch
+fork_no_valid_owners = Hindi mapo-fork ang repository dahil walang mga wastong may-ari.
+use_template = Gamitin ang template na ito
+download_zip = I-download ang ZIP
+download_tar = I-download ang TAR.GZ
+issue_labels = Mga label ng isyu
+generate_repo = I-generate ang repository
+repo_desc_helper = Maglagay ng maikling deskripsyon (opsyonal)
+repo_lang = Wika
+issue_labels_helper = Pumili ng label set ng isyu.
+license = Lisensya
+license_helper = Pumili ng file ng lisensya.
+license_helper_desc = Ang lisensya ay namamahala kung ano ang pwede at hindi pwedeng gawin ng mga ibang tao sa iyong code. Hindi sigurado kung alin ang wasto para sa iyong proyekto? Tignan ang <a target="_blank" rel="noopener noreferrer" href="%s">Pumili ng lisensya.</a>
+object_format_helper = Object format ng repository. Hindi mababago mamaya. Ang SHA1 ang pinaka-compatible.
+readme = README
+auto_init = I-initialize ang repository (Nagdadagdag ng .gitignore, Lisensya, at README)
+trust_model_helper = Pumili ng trust model para sa signature verification. Ang mga posibleng opsyon ay:
+trust_model_helper_collaborator = Katulong: I-trust ang mga signature batay sa mga katulong
+trust_model_helper_committer = Commiter: I-trust ang mga signature na tumutugma sa mga commiter
+trust_model_helper_default = Default: Gamitin ang default trust model para sa installation na ito
+default_branch = Default na branch
+default_branch_label = default
+mirror_prune = Pungusan
+mirror_prune_desc = Tanggalin ang mga antikuwado na sangguni ng remote-tracking
+mirror_address_url_invalid = Ang ibinigay na url ay hindi wasto. Kailangan mong i-escape ang lahat ng mga components ng URL ng tama.
+mirror_address_protocol_invalid = Ang ibinigay na URL ay hindi wasto. Ang http(s):// o git:// na lokasyon lamang ay magagamit para sa pag-mirror.
+mirror_lfs = Large File Storage (LFS)
+mirror_lfs_desc = I-activate ang pag-mirror ng LFS data.
+mirror_lfs_endpoint = Endpoint ng LFS
+mirror_last_synced = Huling na-synchronize
+mirror_password_placeholder = (Hindi nabago)
+mirror_password_blank_placeholder = (Hindi tinakda)
+mirror_password_help = Palitan ang username para burahin ang na-store na password.
+watchers = Mga nanonood
+stargazers = Mga Stargazer
+stars_remove_warning = Tatanggalin nito ang lahat ng mga star sa repository na ito.
+forks = Mga fork
+language_other = Iba
+adopt_preexisting_label = Mag-adopt ng mga file
+adopt_preexisting_content = Gumawa ng repository mula sa %s
+transfer.accept = Tanggapin ang paglipat
+transfer.accept_desc = Ilipat sa "%s"
+transfer.reject = Tanggihan ang paglipat
+transfer.no_permission_to_accept = Wala kang pahintulot para tanggapin ang palilipat na ito.
+transfer.no_permission_to_reject = Wala kang pahintulot para tanggihan ang palilipat na ito.
+desc.private = Pribado
+desc.public = Publiko
+desc.template = Template
+desc.internal = Internal
+template.git_hooks = Mga hook ng Git
+
+[search]
+commit_kind = Maghanap ng mga commit...
+keyword_search_unavailable = Kasalukuyang hindi available ang paghahanap sa pamamagitan ng keyword. Mangyaring makipag-ugnayan sa tagapangasiwa ng site.
+search = Maghanap...
+type_tooltip = Uri ng paghahanap
+fuzzy = Fuzzy
+fuzzy_tooltip = Samahan ang mga resulta na tumutugma rin sa search term nang malapit
+match = Tugma
+match_tooltip = Samahan lang ang mga resulta na tumutugma sa eksaktong search term
+repo_kind = Maghanap ng mga repo...
+user_kind = Maghanap ng mga user...
+org_kind = Maghanap ng mga org...
+team_kind = Maghanap ng mga koponan...
+code_kind = Maghanap ng code...
+code_search_unavailable = Kasalukuyang hindi available ang paghahanap ng code. Mangyaring makipag-ugnayan sa tagapangasiwa ng site.
+package_kind = Maghanap ng mga pakete...
+project_kind = Maghanap ng mga proyekto...
+branch_kind = Maghanap ng mga branch...
+runner_kind = Maghanap ng mga runner...
+no_results = Walang mga tumutugma na resulta na nahanap. \ No newline at end of file
diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini
index 659615e6ff..d3e015a632 100644
--- a/options/locale/locale_fr-FR.ini
+++ b/options/locale/locale_fr-FR.ini
@@ -2317,9 +2317,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu / Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nom d'utilisateur Packagist
diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini
index 27b9a4b17c..e76174cde2 100644
--- a/options/locale/locale_is-IS.ini
+++ b/options/locale/locale_is-IS.ini
@@ -1065,7 +1065,7 @@ settings.web_hook_name_discord=Discord
settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu=Feishu
+settings.web_hook_name_feishu_only =Feishu
settings.title=Heiti
settings.deploy_key_content=Innihald
settings.branches=Greinar
diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini
index dc6f4c127e..d25fc0942d 100644
--- a/options/locale/locale_it-IT.ini
+++ b/options/locale/locale_it-IT.ini
@@ -2085,9 +2085,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu / Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nome utente Packagist
diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini
index 7f3e1ded36..4fd6641dfd 100644
--- a/options/locale/locale_ja-JP.ini
+++ b/options/locale/locale_ja-JP.ini
@@ -55,7 +55,7 @@ repository=リãƒã‚¸ãƒˆãƒª
organization=組織
mirror=ミラー
new_repo=æ–°ã—ã„リãƒã‚¸ãƒˆãƒª
-new_migrate=æ–°ã—ã„移行
+new_migrate=æ–°ã—ã„マイグレーション
new_mirror=æ–°ã—ã„ミラー
new_fork=æ–°ã—ã„フォーク
new_org=æ–°ã—ã„組織
@@ -155,6 +155,8 @@ filter.public = 公開
filter.private = éžå…¬é–‹
toggle_menu = トグルメニュー
filter.not_template = テンプレートã§ã¯ãªã„
+invalid_data = 無効ãªãƒ‡ãƒ¼ã‚¿: %v
+more_items = ã•ã‚‰ã«è¡¨ç¤º
[aria]
navbar=ナビゲーションãƒãƒ¼
@@ -2301,9 +2303,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu / Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist ユーザーå
@@ -3679,3 +3681,8 @@ executable_file=実行å¯èƒ½ãƒ•ã‚¡ã‚¤ãƒ«
symbolic_link=シンボリックリンク
submodule=サブモジュール
+
+
+[search]
+search = 検索...
+type_tooltip = 検索タイプ \ No newline at end of file
diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini
index ce8f05e4b5..20ca341ee4 100644
--- a/options/locale/locale_lv-LV.ini
+++ b/options/locale/locale_lv-LV.ini
@@ -2253,9 +2253,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu / Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist lietotÄjvÄrds
diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini
index 4358cc1e81..d0564d2ebd 100644
--- a/options/locale/locale_nl-NL.ini
+++ b/options/locale/locale_nl-NL.ini
@@ -155,6 +155,8 @@ filter.public = Publiek
filter.private = Privé
filter = Filter
filter.not_archived = Niet gearchiveerd
+more_items = Meer items
+invalid_data = Ongeldige data: %v
[aria]
navbar = Navigatiebalk
@@ -2030,9 +2032,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu / Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist gebruikersnaam
@@ -2686,6 +2688,16 @@ open_with_editor = Open met %s
commits.search_branch = Deze branch
pulls.merged_title_desc_one = heeft %[1]d commit van <code>%[2]s</code> samengevoegd in <code>%[3]s</code> %[4]s
pulls.ready_for_review = Klaar voor een beoordeling?
+editor.push_out_of_date = De push lijkt verouderd.
+editor.commit_id_not_matching = De commit ID komt niet overeen met degene die je aan het bewerken was. Committeer naar een nieuwe branch en voeg dan samen.
+settings.rename_branch_failed_protected = Kan branch %s niet hernoemen omdat het een beschermde branch is.
+stars = Sterren
+n_commit_few = %s commits
+n_branch_one = %s branch
+n_branch_few = %s branches
+n_tag_one = %s tag
+n_tag_few = %s tags
+n_commit_one = %d commit
@@ -3356,6 +3368,7 @@ config_settings = Instellingen
auths.tips.gmail_settings = Gmail instellingen:
config_summary = Samenvatting
config.open_with_editor_app_help = De "Openen met" editors voor het kloonmenu. Als deze leeg blijft, wordt de standaardwaarde gebruikt. Uitvouwen om de standaard te zien.
+auths.tip.gitlab_new = Registreer een nieuwe applicatie op https://gitlab.com/-/profile/applications
[action]
@@ -3704,6 +3717,7 @@ variables.description = Variabelen worden doorgegeven aan bepaalde acties en kun
runners.delete_runner_success = Runner succesvol verwijderd
runs.no_matching_online_runner_helper = Geen overeenkomende online runner met label: %s
runs.workflow = Workflow
+runs.no_job_without_needs = De workflow moet ten minste één taak zonder afhankelijkheden bevatten.
diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini
index 086cea74fa..f2a72c5c7f 100644
--- a/options/locale/locale_pt-BR.ini
+++ b/options/locale/locale_pt-BR.ini
@@ -2236,9 +2236,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu / Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nome de usuário no Packagist
diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini
index 3538ac9460..552695a455 100644
--- a/options/locale/locale_pt-PT.ini
+++ b/options/locale/locale_pt-PT.ini
@@ -2269,9 +2269,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu / Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nome de utilizador no Packagist
diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini
index addc2f8110..e9daba082e 100644
--- a/options/locale/locale_ru-RU.ini
+++ b/options/locale/locale_ru-RU.ini
@@ -155,6 +155,8 @@ filter.public = Публичные
filter.private = Приватные
filter.is_archived = Ðрхивированные
filter.not_mirror = Ðе зеркала
+more_items = Больше Ñлементов
+invalid_data = Ðеверные данные: %v
[aria]
navbar=Панель навигации
@@ -2276,9 +2278,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu или Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu или Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Packagist
@@ -2685,6 +2687,13 @@ settings.mirror_settings.docs.doc_link_pull_section = раздел докумеÐ
wiki.original_git_entry_tooltip = Перейти по наÑтоÑщему пути вмеÑто читабельной ÑÑылки.
open_with_editor = Открыть в %s
commits.search_branch = Ð’ Ñтой ветке
+stars = Добавившие в избранное
+n_tag_one = %s тег
+n_branch_few = %s веток
+n_commit_few = %s коммитов
+n_commit_one = %s коммит
+n_tag_few = %s тегов
+n_branch_one = %s ветка
[graphs]
@@ -3352,6 +3361,7 @@ config_summary = Сводка
config.open_with_editor_app_help = ÐŸÑ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð´Ð»Ñ "Открыть в" в меню. ОÑтавьте пуÑтым Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ð¹ по умолчанию. Разверните Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра.
config_settings = ÐаÑтройки
auths.tips.gmail_settings = ÐаÑтройки Gmail:
+auths.tip.gitlab_new = Создайте новое приложение в https://gitlab.com/-/profile/applications
[action]
@@ -3596,6 +3606,7 @@ rpm.repository = О репозитории
rpm.repository.architectures = Ðрхитектуры
rpm.repository.multiple_groups = Этот пакет доÑтупен в неÑкольких группах.
owner.settings.chef.keypair.description = Ð”Ð»Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ð¸ рееÑтра Chef необходима пара ключей. ЕÑли до Ñтого вы уже Ñгенерировали пару ключей, Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ð½Ð¾Ð²Ð¾Ð¹ приведёт к прекращению дейÑÑ‚Ð²Ð¸Ñ Ð¿Ñ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰ÐµÐ¹.
+owner.settings.cargo.rebuild.no_index = Ðевозможно выполнить переÑборку. Ðет инициализированного индекÑа.
[secrets]
secrets=Секреты
@@ -3747,4 +3758,5 @@ commit_kind = ПоиÑк коммитов...
no_results = По запроÑу ничего не найдено.
keyword_search_unavailable = ПоиÑк по ключевым Ñловам недоÑтупен. Уточните подробноÑти у админиÑтратора.
match_tooltip = Включать только результаты, точно ÑоответÑтвующие запроÑу
-code_search_unavailable = ПоиÑк по коду ÑÐµÐ¹Ñ‡Ð°Ñ Ð½ÐµÐ´Ð¾Ñтупен. Уточните подробноÑти у админиÑтратора. \ No newline at end of file
+code_search_unavailable = ПоиÑк по коду ÑÐµÐ¹Ñ‡Ð°Ñ Ð½ÐµÐ´Ð¾Ñтупен. Уточните подробноÑти у админиÑтратора.
+runner_kind = ПоиÑк раннеров... \ No newline at end of file
diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini
index 74ef77eb19..b6bb510ab4 100644
--- a/options/locale/locale_tr-TR.ini
+++ b/options/locale/locale_tr-TR.ini
@@ -2252,9 +2252,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu / Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist kullanıcı adı
diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index 4ca2e70f21..c5a5aabb97 100644
--- a/options/locale/locale_zh-CN.ini
+++ b/options/locale/locale_zh-CN.ini
@@ -155,6 +155,8 @@ filter.not_template = éžæ¨¡æ¿
filter.public = 公开
filter.private = ç§æœ‰
toggle_menu = èœå•
+invalid_data = 无效数æ®: %v
+more_items = 显示更多
[aria]
navbar=导航æ 
@@ -2316,9 +2318,9 @@ settings.web_hook_name_dingtalk=钉钉
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=飞书 / Lark Suite
-settings.web_hook_name_feishu=飞书
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=飞书 / Lark Suite
+settings.web_hook_name_feishu_only =飞书
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=ä¼ä¸šå¾®ä¿¡
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist 用户å
@@ -2688,6 +2690,11 @@ activity.navbar.code_frequency = 代ç é¢‘率
activity.navbar.recent_commits = 近期æ交
pulls.agit_explanation = 该åˆå¹¶è¯·æ±‚是用 AGit 创建的。AGit 是一ç§å¯ä»¥è®©è´¡çŒ®è€…直接通过 “git push†æ出更改代ç è€Œä¸éœ€è¦æ´¾ç”Ÿæˆ–建立新分支。
error.broken_git_hook = 该仓库的 Git é’©å­ä¼¼ä¹Žå·²ç»æŸå,请按照 <a target="_blank" rel="noreferrer" href="%s">此文档</a>æ¥ä¿®å¤è¿™äº›é—®é¢˜ï¼Œç„¶åŽæŽ¨é€ä¸€äº›æ交æ¥åˆ·æ–°çŠ¶æ€ã€‚
+pulls.merged_title_desc_one = 已将æ¥è‡ª <code>%[2]s</code> çš„ %[1]d æ交åˆå¹¶å…¥ <code>%[3]s</code> %[4]s
+commits.search_branch = 此分支
+open_with_editor = 使用 %s 打开
+pulls.title_desc_one = 想è¦å°†æ¥è‡ª <code>%[2]s</code> çš„ %[1]d æ交åˆå¹¶åˆ° <code id="branch_target">%[3]s</code>
+settings.rename_branch_failed_protected = 无法é‡å‘½åå—ä¿æŠ¤çš„分支 %s。
[graphs]
component_loading=正在加载 %s...
@@ -2958,7 +2965,7 @@ users.max_repo_creation_desc=(设置为 -1 表示使用全局默认值)
users.is_activated=该用户已被激活
users.prohibit_login=ç¦ç”¨ç™»å½•
users.is_admin=是管ç†å‘˜
-users.is_restricted=å—é™åˆ¶çš„
+users.is_restricted=å—é™
users.allow_git_hook=å…许创建 Git é’©å­
users.allow_git_hook_tooltip=Git é’©å­å°†ä¼šè¢«ä»¥æ“作系统用户è¿è¡Œï¼Œå°†ä¼šæ‹¥æœ‰åŒæ ·çš„主机访问æƒé™ã€‚因此,拥有此特殊的Git é’©å­æƒé™å°†èƒ½å¤Ÿè®¿é—®åˆä¿®æ”¹æ‰€æœ‰çš„ Forgejo 仓库或者Forgejoçš„æ•°æ®åº“。åŒæ—¶ä¹Ÿèƒ½èŽ·å¾—Forgejo的管ç†å‘˜æƒé™ã€‚
users.allow_import_local=å…许导入本地仓库
@@ -3370,6 +3377,8 @@ self_check.database_inconsistent_collation_columns=æ•°æ®åº“正在使用%s的排
self_check.database_fix_mysql=对于MySQL/MariaDB用户,您å¯ä»¥ä½¿ç”¨â€œgitea doctor convertâ€å‘½ä»¤æ¥è§£å†³æ ¡éªŒé—®é¢˜ã€‚ 或者您也å¯ä»¥é€šè¿‡ "ALTER ... COLLATE ..." 这样的SQL æ¥æ‰‹åŠ¨è§£å†³è¿™ä¸ªé—®é¢˜ã€‚
self_check.database_fix_mssql=对于MSSQL用户,您现在åªèƒ½é€šè¿‡"ALTER ... COLLATE ..."SQLs手动解决这个问题。
auths.tips.gmail_settings = Gmail 设置:
+auths.tip.gitlab_new = 在 https://gitlab.com/-/profile/applications 上注册新应用
+config_settings = 设置
[action]
create_repo=创建了仓库 <a href="%s">%s</a>
@@ -3738,3 +3747,22 @@ executable_file=å¯æ‰§è¡Œæ–‡ä»¶
symbolic_link=符å·é“¾æŽ¥
submodule=å­æ¨¡å—
+
+
+[search]
+keyword_search_unavailable = 关键è¯æœç´¢ç›®å‰ä¸å¯ç”¨ï¼Œè¯·è”系站点管ç†å‘˜ã€‚
+search = æœç´¢...
+repo_kind = æœç´¢ä»“库...
+user_kind = æœç´¢ç”¨æˆ·...
+org_kind = æœç´¢ç»„织...
+team_kind = æœç´¢å›¢é˜Ÿ...
+code_kind = æœç´¢ä»£ç ...
+code_search_unavailable = 代ç æœç´¢ç›®å‰ä¸å¯ç”¨ï¼Œè¯·è”系站点管ç†å‘˜ã€‚
+package_kind = æœç´¢è½¯ä»¶åŒ…...
+project_kind = æœç´¢é¡¹ç›®...
+branch_kind = æœç´¢åˆ†æ”¯...
+commit_kind = æœç´¢æ交...
+runner_kind = æœç´¢Runners...
+no_results = 未找到匹é…的结果。
+type_tooltip = æœç´¢ç±»åž‹
+fuzzy = 模糊 \ No newline at end of file
diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini
index 5bfff77fd2..a2e3f512ff 100644
--- a/options/locale/locale_zh-TW.ini
+++ b/options/locale/locale_zh-TW.ini
@@ -2071,9 +2071,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
-settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
-settings.web_hook_name_feishu=Feishu
-settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_feishu=Feishu / Lark Suite
+settings.web_hook_name_feishu_only =Feishu
+settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist 帳號
diff --git a/package-lock.json b/package-lock.json
index f6df16f2a5..c4579fbd8a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,10 +11,10 @@
"@citation-js/plugin-software-formats": "0.6.1",
"@claviska/jquery-minicolors": "2.3.6",
"@github/markdown-toolbar-element": "2.2.3",
- "@github/relative-time-element": "4.3.1",
+ "@github/relative-time-element": "4.4.0",
"@github/text-expander-element": "2.6.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
- "@primer/octicons": "19.8.0",
+ "@primer/octicons": "19.9.0",
"add-asset-webpack-plugin": "2.0.1",
"ansi_up": "6.0.2",
"asciinema-player": "3.7.1",
@@ -40,7 +40,7 @@
"monaco-editor": "0.47.0",
"monaco-editor-webpack-plugin": "7.1.0",
"pdfobject": "2.3.0",
- "postcss": "8.4.35",
+ "postcss": "8.4.38",
"postcss-loader": "8.1.1",
"postcss-nesting": "12.1.0",
"pretty-ms": "9.0.0",
@@ -59,7 +59,7 @@
"vue-chartjs": "5.3.0",
"vue-loader": "17.4.2",
"vue3-calendar-heatmap": "2.0.5",
- "webpack": "5.90.3",
+ "webpack": "5.91.0",
"webpack-cli": "5.1.4",
"wrap-ansi": "9.0.0"
},
@@ -70,6 +70,7 @@
"@stylistic/eslint-plugin-js": "1.7.0",
"@stylistic/stylelint-plugin": "2.1.0",
"@vitejs/plugin-vue": "5.0.4",
+ "@vue/test-utils": "2.4.5",
"eslint": "8.57.0",
"eslint-plugin-array-func": "4.0.0",
"eslint-plugin-github": "4.10.2",
@@ -77,24 +78,24 @@
"eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0",
"eslint-plugin-no-use-extend-native": "0.5.0",
- "eslint-plugin-regexp": "2.3.0",
+ "eslint-plugin-regexp": "2.4.0",
"eslint-plugin-sonarjs": "0.24.0",
"eslint-plugin-unicorn": "51.0.1",
- "eslint-plugin-vitest": "0.3.26",
- "eslint-plugin-vitest-globals": "1.4.0",
- "eslint-plugin-vue": "9.23.0",
- "eslint-plugin-vue-scoped-css": "2.7.2",
+ "eslint-plugin-vitest": "0.4.0",
+ "eslint-plugin-vitest-globals": "1.5.0",
+ "eslint-plugin-vue": "9.24.0",
+ "eslint-plugin-vue-scoped-css": "2.8.0",
"eslint-plugin-wc": "2.0.4",
- "happy-dom": "14.2.0",
+ "happy-dom": "14.3.9",
"markdownlint-cli": "0.39.0",
"postcss-html": "1.6.0",
- "stylelint": "16.2.1",
+ "stylelint": "16.3.0",
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
"stylelint-declaration-strict-value": "1.10.4",
"svgo": "3.2.0",
- "updates": "15.3.1",
+ "updates": "16.0.0",
"vite-string-plugin": "1.1.5",
- "vitest": "1.3.1"
+ "vitest": "1.4.0"
},
"engines": {
"node": ">= 18.0.0"
@@ -516,6 +517,16 @@
"node": ">=10.0.0"
}
},
+ "node_modules/@dual-bundle/import-meta-resolve": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz",
+ "integrity": "sha512-ZKXyJeFAzcpKM2kk8ipoGIPUqx9BX52omTGnfwjJvxOCaZTM2wtDK7zN0aIgPRbT9XYAlha0HtmZ+XKteuh0Gw==",
+ "dev": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
@@ -994,9 +1005,9 @@
"integrity": "sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A=="
},
"node_modules/@github/relative-time-element": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.3.1.tgz",
- "integrity": "sha512-zL79nlhZVCg7x2Pf/HT5MB0mowmErE71VXpF10/3Wy8dQwkninNO1M9aOizh2wKC5LkSpDXqNYjDZwbH0/bcSg=="
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.0.tgz",
+ "integrity": "sha512-CrI6oAecoahG7PF5dsgjdvlF5kCtusVMjg810EULD81TvnDsP+k/FRi/ClFubWLgBo4EGpr2EfvmumtqQFo7ow=="
},
"node_modules/@github/text-expander-element": {
"version": "2.6.1",
@@ -1319,6 +1330,12 @@
"node": ">= 8"
}
},
+ "node_modules/@one-ini/wasm": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
+ "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
+ "dev": true
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -1365,9 +1382,9 @@
}
},
"node_modules/@primer/octicons": {
- "version": "19.8.0",
- "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.8.0.tgz",
- "integrity": "sha512-Imze/fyW41Io5fN+27T5EAeXJrgBjMbz6nzU+wYbRylXvIAjLPUvaJPVoStiFlgSU+TjTUJqg5A9rgMDzTyMCg==",
+ "version": "19.9.0",
+ "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.9.0.tgz",
+ "integrity": "sha512-uAZa9cMgWkzbEsZnYWB7tg0vt7QprubD7ljtprz2fBJ8CjyqoxFRRsFvH4UiJdjK/3o87ODgDkhiflyJXDh+Lg==",
"dependencies": {
"object-assign": "^4.1.1"
}
@@ -2238,16 +2255,16 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz",
- "integrity": "sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz",
+ "integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
- "@typescript-eslint/scope-manager": "7.3.1",
- "@typescript-eslint/type-utils": "7.3.1",
- "@typescript-eslint/utils": "7.3.1",
- "@typescript-eslint/visitor-keys": "7.3.1",
+ "@typescript-eslint/scope-manager": "7.4.0",
+ "@typescript-eslint/type-utils": "7.4.0",
+ "@typescript-eslint/utils": "7.4.0",
+ "@typescript-eslint/visitor-keys": "7.4.0",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
@@ -2273,15 +2290,15 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.3.1.tgz",
- "integrity": "sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz",
+ "integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "7.3.1",
- "@typescript-eslint/types": "7.3.1",
- "@typescript-eslint/typescript-estree": "7.3.1",
- "@typescript-eslint/visitor-keys": "7.3.1",
+ "@typescript-eslint/scope-manager": "7.4.0",
+ "@typescript-eslint/types": "7.4.0",
+ "@typescript-eslint/typescript-estree": "7.4.0",
+ "@typescript-eslint/visitor-keys": "7.4.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2301,13 +2318,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz",
- "integrity": "sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz",
+ "integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "7.3.1",
- "@typescript-eslint/visitor-keys": "7.3.1"
+ "@typescript-eslint/types": "7.4.0",
+ "@typescript-eslint/visitor-keys": "7.4.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -2318,13 +2335,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz",
- "integrity": "sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz",
+ "integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==",
"dev": true,
"dependencies": {
- "@typescript-eslint/typescript-estree": "7.3.1",
- "@typescript-eslint/utils": "7.3.1",
+ "@typescript-eslint/typescript-estree": "7.4.0",
+ "@typescript-eslint/utils": "7.4.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
@@ -2345,9 +2362,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.3.1.tgz",
- "integrity": "sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz",
+ "integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==",
"dev": true,
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -2358,13 +2375,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz",
- "integrity": "sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz",
+ "integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "7.3.1",
- "@typescript-eslint/visitor-keys": "7.3.1",
+ "@typescript-eslint/types": "7.4.0",
+ "@typescript-eslint/visitor-keys": "7.4.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -2386,17 +2403,17 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.3.1.tgz",
- "integrity": "sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz",
+ "integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
- "@typescript-eslint/scope-manager": "7.3.1",
- "@typescript-eslint/types": "7.3.1",
- "@typescript-eslint/typescript-estree": "7.3.1",
+ "@typescript-eslint/scope-manager": "7.4.0",
+ "@typescript-eslint/types": "7.4.0",
+ "@typescript-eslint/typescript-estree": "7.4.0",
"semver": "^7.5.4"
},
"engines": {
@@ -2411,12 +2428,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz",
- "integrity": "sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz",
+ "integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "7.3.1",
+ "@typescript-eslint/types": "7.4.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -2447,13 +2464,13 @@
}
},
"node_modules/@vitest/expect": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz",
- "integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz",
+ "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==",
"dev": true,
"dependencies": {
- "@vitest/spy": "1.3.1",
- "@vitest/utils": "1.3.1",
+ "@vitest/spy": "1.4.0",
+ "@vitest/utils": "1.4.0",
"chai": "^4.3.10"
},
"funding": {
@@ -2461,12 +2478,12 @@
}
},
"node_modules/@vitest/runner": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz",
- "integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz",
+ "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==",
"dev": true,
"dependencies": {
- "@vitest/utils": "1.3.1",
+ "@vitest/utils": "1.4.0",
"p-limit": "^5.0.0",
"pathe": "^1.1.1"
},
@@ -2502,9 +2519,9 @@
}
},
"node_modules/@vitest/snapshot": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz",
- "integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz",
+ "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==",
"dev": true,
"dependencies": {
"magic-string": "^0.30.5",
@@ -2528,9 +2545,9 @@
}
},
"node_modules/@vitest/spy": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz",
- "integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz",
+ "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==",
"dev": true,
"dependencies": {
"tinyspy": "^2.2.0"
@@ -2540,9 +2557,9 @@
}
},
"node_modules/@vitest/utils": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz",
- "integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz",
+ "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==",
"dev": true,
"dependencies": {
"diff-sequences": "^29.6.3",
@@ -2670,6 +2687,16 @@
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
"integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
},
+ "node_modules/@vue/test-utils": {
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.5.tgz",
+ "integrity": "sha512-oo2u7vktOyKUked36R93NB7mg2B+N7Plr8lxp2JBGwr18ch6EggFjixSCdIVVLkT6Qr0z359Xvnafc9dcKyDUg==",
+ "dev": true,
+ "dependencies": {
+ "js-beautify": "^1.14.9",
+ "vue-component-type-helpers": "^2.0.0"
+ }
+ },
"node_modules/@webassemblyjs/ast": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
@@ -2852,6 +2879,15 @@
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
},
+ "node_modules/abbrev": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
+ "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
+ "dev": true,
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -3475,9 +3511,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001599",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz",
- "integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==",
+ "version": "1.0.30001600",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz",
+ "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==",
"funding": [
{
"type": "opencollective",
@@ -3789,6 +3825,22 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
+ "node_modules/config-chain": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
+ "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
+ "dev": true,
+ "dependencies": {
+ "ini": "^1.3.4",
+ "proto-list": "~1.2.1"
+ }
+ },
+ "node_modules/config-chain/node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true
+ },
"node_modules/core-js-compat": {
"version": "3.36.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz",
@@ -4721,9 +4773,9 @@
}
},
"node_modules/dompurify": {
- "version": "3.0.10",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.10.tgz",
- "integrity": "sha512-WZDL8ZHTliEVP3Lk4phtvjg8SNQ3YMc5WVstxE8cszKZrFjzI4PF4ZTIk9VGAc9vZADO7uGO2V/ZiStcRSAT4Q=="
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.11.tgz",
+ "integrity": "sha512-Fan4uMuyB26gFV3ovPoEoQbxRRPfTu3CvImyZnhGq5fsIEO+gEFLp45ISFt+kQBWsK5ulDdT0oV28jS1UrwQLg=="
},
"node_modules/domutils": {
"version": "3.1.0",
@@ -4765,10 +4817,52 @@
"marked": "^4.1.0"
}
},
+ "node_modules/editorconfig": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
+ "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
+ "dev": true,
+ "dependencies": {
+ "@one-ini/wasm": "0.1.1",
+ "commander": "^10.0.0",
+ "minimatch": "9.0.1",
+ "semver": "^7.5.3"
+ },
+ "bin": {
+ "editorconfig": "bin/editorconfig"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/editorconfig/node_modules/commander": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/editorconfig/node_modules/minimatch": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
+ "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/electron-to-chromium": {
- "version": "1.4.713",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.713.tgz",
- "integrity": "sha512-vDarADhwntXiULEdmWd77g2dV6FrNGa8ecAC29MZ4TwPut2fvosD0/5sJd1qWNNe8HcJFAC+F5Lf9jW1NPtWmw=="
+ "version": "1.4.716",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.716.tgz",
+ "integrity": "sha512-t/MXMzFKQC3UfMDpw7V5wdB/UAB8dWx4hEsy+fpPYJWW3gqh3u5T1uXp6vR+H6dGCPBxkRo+YBcapBLvbGQHRw=="
},
"node_modules/elkjs": {
"version": "0.9.2",
@@ -4899,19 +4993,19 @@
}
},
"node_modules/es-aggregate-error": {
- "version": "1.0.12",
- "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.12.tgz",
- "integrity": "sha512-j0PupcmELoVbYS2NNrsn5zcLLEsryQwP02x8fRawh7c2eEaPHwJFAxltZsqV7HJjsF57+SMpYyVRWgbVLfOagg==",
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.13.tgz",
+ "integrity": "sha512-KkzhUUuD2CUMqEc8JEqsXEMDHzDPE8RCjZeUBitsnB1eNcAJWQPiciKsMXe3Yytj4Flw1XLl46Qcf9OxvZha7A==",
"dev": true,
"dependencies": {
- "define-data-property": "^1.1.1",
+ "define-data-property": "^1.1.4",
"define-properties": "^1.2.1",
- "es-abstract": "^1.22.3",
- "es-errors": "^1.1.0",
+ "es-abstract": "^1.23.2",
+ "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"globalthis": "^1.0.3",
- "has-property-descriptors": "^1.0.1",
- "set-function-name": "^2.0.1"
+ "has-property-descriptors": "^1.0.2",
+ "set-function-name": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -4967,9 +5061,9 @@
}
},
"node_modules/es-module-lexer": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.2.tgz",
- "integrity": "sha512-7nOqkomXZEaxUDJw21XZNtRk739QvrPSoZoRtbsEfcii00vdzZUh6zh1CQwHhrib8MdEtJfv5rJiGeb4KuV/vw=="
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz",
+ "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw=="
},
"node_modules/es-object-atoms": {
"version": "1.0.0",
@@ -5164,9 +5258,9 @@
}
},
"node_modules/eslint-compat-utils": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.4.1.tgz",
- "integrity": "sha512-5N7ZaJG5pZxUeNNJfUchurLVrunD1xJvyg5kYOIVF8kg1f3ajTikmAu/5fZ9w100omNPOoMjngRszh/Q/uFGMg==",
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.0.tgz",
+ "integrity": "sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==",
"dev": true,
"dependencies": {
"semver": "^7.5.4"
@@ -5598,9 +5692,9 @@
}
},
"node_modules/eslint-plugin-regexp": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.3.0.tgz",
- "integrity": "sha512-T8JUs7ssRGbuXb+CGfdUJbcxTBMCNOpNqNBLuC8JUKAEIez72J37RaOi5/4dAUsGz92GbWVtqTLPSJZGyP/sQA==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.4.0.tgz",
+ "integrity": "sha512-OL2S6VPjQhs9s/NclQ0qattVq1J0GU8ox70/HIVy5Dxw+qbbdd7KQkyucsez2clEQjvdtDe12DTnPphFFUyXFg==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
@@ -5664,12 +5758,12 @@
}
},
"node_modules/eslint-plugin-vitest": {
- "version": "0.3.26",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.3.26.tgz",
- "integrity": "sha512-oxe5JSPgRjco8caVLTh7Ti8PxpwJdhSV0hTQAmkFcNcmy/9DnqLB/oNVRA11RmVRP//2+jIIT6JuBEcpW3obYg==",
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.4.0.tgz",
+ "integrity": "sha512-3oWgZIwdWVBQ5plvkmOBjreIGLQRdYb7x54OP8uIRHeZyRVJIdOn9o/qWVb9292fDMC8jn7H7d9TSFBZqhrykQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/utils": "^7.1.1"
+ "@typescript-eslint/utils": "^7.2.0"
},
"engines": {
"node": "^18.0.0 || >= 20.0.0"
@@ -5688,18 +5782,19 @@
}
},
"node_modules/eslint-plugin-vitest-globals": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vitest-globals/-/eslint-plugin-vitest-globals-1.4.0.tgz",
- "integrity": "sha512-WE+YlK9X9s4vf5EaYRU0Scw7WItDZStm+PapFSYlg2ABNtaQ4zIG7wEqpoUB3SlfM+SgkhgmzR0TeJOO5k3/Nw==",
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vitest-globals/-/eslint-plugin-vitest-globals-1.5.0.tgz",
+ "integrity": "sha512-ZSsVOaOIig0oVLzRTyk8lUfBfqzWxr/J3/NFMfGGRIkGQPejJYmDH3gXmSJxAojts77uzAGB/UmVrwi2DC4LYA==",
"dev": true
},
"node_modules/eslint-plugin-vue": {
- "version": "9.23.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.23.0.tgz",
- "integrity": "sha512-Bqd/b7hGYGrlV+wP/g77tjyFmp81lh5TMw0be9093X02SyelxRRfCI6/IsGq/J7Um0YwB9s0Ry0wlFyjPdmtUw==",
+ "version": "9.24.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.24.0.tgz",
+ "integrity": "sha512-9SkJMvF8NGMT9aQCwFc5rj8Wo1XWSMSHk36i7ZwdI614BU7sIOR28ZjuFPKp8YGymZN12BSEbiSwa7qikp+PBw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
+ "globals": "^13.24.0",
"natural-compare": "^1.4.0",
"nth-check": "^2.1.1",
"postcss-selector-parser": "^6.0.15",
@@ -5715,13 +5810,13 @@
}
},
"node_modules/eslint-plugin-vue-scoped-css": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vue-scoped-css/-/eslint-plugin-vue-scoped-css-2.7.2.tgz",
- "integrity": "sha512-myJ99CJuwmAx5kq1WjgIeaUkxeU6PIEUh7age79Alm30bhN4fVTapOQLSMlvVTgxr36Y3igsZ3BCJM32LbHHig==",
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue-scoped-css/-/eslint-plugin-vue-scoped-css-2.8.0.tgz",
+ "integrity": "sha512-JXb3Um4+AhuDGxSX6FAGCI0p811xF7W8L7yxC8wmAEZEI/teTjlpC09noqQZHXn53RZ/TGQJ8Onaq4teYLxBbg==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
- "eslint-compat-utils": "^0.4.0",
+ "eslint-compat-utils": "^0.5.0",
"lodash": "^4.17.21",
"postcss": "^8.4.31",
"postcss-safe-parser": "^6.0.0",
@@ -6502,9 +6597,9 @@
}
},
"node_modules/happy-dom": {
- "version": "14.2.0",
- "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.2.0.tgz",
- "integrity": "sha512-vTqF/9MEkRKgYy5eKq9W0uiNmkgnVAmJhRwn8x8fQBR7lc4C84859jLhgZ1lR4Gi/t70oSdgvtLpxlHjgdJrAw==",
+ "version": "14.3.9",
+ "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.3.9.tgz",
+ "integrity": "sha512-0kPQchwthekcYpYN8CvCiq+/z5bqFYDLbTxZ+yDLwT8AFRVJDFadShHRxp3VAZRy7a5isOZ1j/LzsU1dtAIZMQ==",
"dev": true,
"dependencies": {
"entities": "^4.5.0",
@@ -7387,6 +7482,58 @@
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
},
+ "node_modules/js-beautify": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz",
+ "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==",
+ "dev": true,
+ "dependencies": {
+ "config-chain": "^1.1.13",
+ "editorconfig": "^1.0.4",
+ "glob": "^10.3.3",
+ "js-cookie": "^3.0.5",
+ "nopt": "^7.2.0"
+ },
+ "bin": {
+ "css-beautify": "js/bin/css-beautify.js",
+ "html-beautify": "js/bin/html-beautify.js",
+ "js-beautify": "js/bin/js-beautify.js"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/js-beautify/node_modules/glob": {
+ "version": "10.3.12",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
+ "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
+ "dev": true,
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^2.3.6",
+ "minimatch": "^9.0.1",
+ "minipass": "^7.0.4",
+ "path-scurry": "^1.10.2"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/js-cookie": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
+ "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/js-levenshtein-esm": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz",
@@ -7584,9 +7731,9 @@
}
},
"node_modules/known-css-properties": {
- "version": "0.29.0",
- "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz",
- "integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==",
+ "version": "0.30.0",
+ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.30.0.tgz",
+ "integrity": "sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==",
"dev": true
},
"node_modules/language-subtag-registry": {
@@ -8790,6 +8937,21 @@
"resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
"integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="
},
+ "node_modules/nopt": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz",
+ "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==",
+ "dev": true,
+ "dependencies": {
+ "abbrev": "^2.0.0"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@@ -9129,11 +9291,11 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/path-scurry": {
- "version": "1.10.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
- "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
+ "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
"dependencies": {
- "lru-cache": "^9.1.1 || ^10.0.0",
+ "lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
@@ -9346,9 +9508,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.35",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
- "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"funding": [
{
"type": "opencollective",
@@ -9366,7 +9528,7 @@
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
+ "source-map-js": "^1.2.0"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -9716,6 +9878,12 @@
"integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==",
"dev": true
},
+ "node_modules/proto-list": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
+ "dev": true
+ },
"node_modules/proto-props": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/proto-props/-/proto-props-2.0.0.tgz",
@@ -10676,14 +10844,17 @@
}
},
"node_modules/string.prototype.trimstart": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz",
- "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==",
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+ "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.2.0",
- "es-abstract": "^1.22.1"
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -10776,15 +10947,16 @@
"dev": true
},
"node_modules/stylelint": {
- "version": "16.2.1",
- "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.2.1.tgz",
- "integrity": "sha512-SfIMGFK+4n7XVAyv50CpVfcGYWG4v41y6xG7PqOgQSY8M/PgdK0SQbjWFblxjJZlN9jNq879mB4BCZHJRIJ1hA==",
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.3.0.tgz",
+ "integrity": "sha512-hqC6xNTbQ5HRGQXfIW4HwXcx09raIFz4W4XFbraeqWqYRVVY/ibYvI0dsu0ORMQY8re2bpDdCAeIa2cm+QJ4Sw==",
"dev": true,
"dependencies": {
- "@csstools/css-parser-algorithms": "^2.5.0",
- "@csstools/css-tokenizer": "^2.2.3",
- "@csstools/media-query-list-parser": "^2.1.7",
- "@csstools/selector-specificity": "^3.0.1",
+ "@csstools/css-parser-algorithms": "^2.6.1",
+ "@csstools/css-tokenizer": "^2.2.4",
+ "@csstools/media-query-list-parser": "^2.1.9",
+ "@csstools/selector-specificity": "^3.0.2",
+ "@dual-bundle/import-meta-resolve": "^4.0.0",
"balanced-match": "^2.0.0",
"colord": "^2.9.3",
"cosmiconfig": "^9.0.0",
@@ -10798,19 +10970,19 @@
"globby": "^11.1.0",
"globjoin": "^0.1.4",
"html-tags": "^3.3.1",
- "ignore": "^5.3.0",
+ "ignore": "^5.3.1",
"imurmurhash": "^0.1.4",
"is-plain-object": "^5.0.0",
- "known-css-properties": "^0.29.0",
+ "known-css-properties": "^0.30.0",
"mathml-tag-names": "^2.1.3",
- "meow": "^13.1.0",
+ "meow": "^13.2.0",
"micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
"picocolors": "^1.0.0",
- "postcss": "^8.4.33",
+ "postcss": "^8.4.38",
"postcss-resolve-nested-selector": "^0.1.1",
"postcss-safe-parser": "^7.0.0",
- "postcss-selector-parser": "^6.0.15",
+ "postcss-selector-parser": "^6.0.16",
"postcss-value-parser": "^4.2.0",
"resolve-from": "^5.0.0",
"string-width": "^4.2.3",
@@ -11418,9 +11590,9 @@
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="
},
"node_modules/tinypool": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz",
- "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==",
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.3.tgz",
+ "integrity": "sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==",
"dev": true,
"engines": {
"node": ">=14.0.0"
@@ -11611,9 +11783,9 @@
}
},
"node_modules/typed-array-length": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz",
- "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==",
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz",
+ "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.7",
@@ -11737,12 +11909,12 @@
}
},
"node_modules/updates": {
- "version": "15.3.1",
- "resolved": "https://registry.npmjs.org/updates/-/updates-15.3.1.tgz",
- "integrity": "sha512-DqHT1aJ6p6jVLWRiAeuVx/TQotvEwUjgrY1Mlc0a2qYk+eKEQVXugQ4M+6QoVMA3X1NFAVsb02d93pmWam4bBA==",
+ "version": "16.0.0",
+ "resolved": "https://registry.npmjs.org/updates/-/updates-16.0.0.tgz",
+ "integrity": "sha512-Ra3QUu/rfbSCsG83zNNvoRQt0FVT3qULBSALYTlwTDX6oyz7R5GQAYwqJoIG/RDjYAXpwr3usrInoyHHTP6cag==",
"dev": true,
"bin": {
- "updates": "bin/updates.js"
+ "updates": "dist/updates.js"
},
"engines": {
"node": ">=18"
@@ -11825,9 +11997,9 @@
}
},
"node_modules/vite": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.2.tgz",
- "integrity": "sha512-FWZbz0oSdLq5snUI0b6sULbz58iXFXdvkZfZWR/F0ZJuKTSPO7v72QPXt6KqYeMFb0yytNp6kZosxJ96Nr/wDQ==",
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz",
+ "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==",
"dev": true,
"dependencies": {
"esbuild": "^0.20.1",
@@ -11880,9 +12052,9 @@
}
},
"node_modules/vite-node": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz",
- "integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz",
+ "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==",
"dev": true,
"dependencies": {
"cac": "^6.7.14",
@@ -11927,34 +12099,6 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/vite/node_modules/postcss": {
- "version": "8.4.38",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
- "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "nanoid": "^3.3.7",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.2.0"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
"node_modules/vite/node_modules/rollup": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz",
@@ -11988,16 +12132,16 @@
}
},
"node_modules/vitest": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz",
- "integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz",
+ "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==",
"dev": true,
"dependencies": {
- "@vitest/expect": "1.3.1",
- "@vitest/runner": "1.3.1",
- "@vitest/snapshot": "1.3.1",
- "@vitest/spy": "1.3.1",
- "@vitest/utils": "1.3.1",
+ "@vitest/expect": "1.4.0",
+ "@vitest/runner": "1.4.0",
+ "@vitest/snapshot": "1.4.0",
+ "@vitest/spy": "1.4.0",
+ "@vitest/utils": "1.4.0",
"acorn-walk": "^8.3.2",
"chai": "^4.3.10",
"debug": "^4.3.4",
@@ -12011,7 +12155,7 @@
"tinybench": "^2.5.1",
"tinypool": "^0.8.2",
"vite": "^5.0.0",
- "vite-node": "1.3.1",
+ "vite-node": "1.4.0",
"why-is-node-running": "^2.2.2"
},
"bin": {
@@ -12026,8 +12170,8 @@
"peerDependencies": {
"@edge-runtime/vm": "*",
"@types/node": "^18.0.0 || >=20.0.0",
- "@vitest/browser": "1.3.1",
- "@vitest/ui": "1.3.1",
+ "@vitest/browser": "1.4.0",
+ "@vitest/ui": "1.4.0",
"happy-dom": "*",
"jsdom": "*"
},
@@ -12102,6 +12246,12 @@
"vue": "^3.0.0-0 || ^2.7.0"
}
},
+ "node_modules/vue-component-type-helpers": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.7.tgz",
+ "integrity": "sha512-7e12Evdll7JcTIocojgnCgwocX4WzIYStGClBQ+QuWPinZo/vQolv2EMq4a3lg16TKfwWafLimG77bxb56UauA==",
+ "dev": true
+ },
"node_modules/vue-eslint-parser": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz",
@@ -12186,25 +12336,25 @@
}
},
"node_modules/webpack": {
- "version": "5.90.3",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz",
- "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==",
+ "version": "5.91.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz",
+ "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==",
"dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.5",
- "@webassemblyjs/ast": "^1.11.5",
- "@webassemblyjs/wasm-edit": "^1.11.5",
- "@webassemblyjs/wasm-parser": "^1.11.5",
+ "@webassemblyjs/ast": "^1.12.1",
+ "@webassemblyjs/wasm-edit": "^1.12.1",
+ "@webassemblyjs/wasm-parser": "^1.12.1",
"acorn": "^8.7.1",
"acorn-import-assertions": "^1.9.0",
"browserslist": "^4.21.10",
"chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^5.15.0",
+ "enhanced-resolve": "^5.16.0",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
"glob-to-regexp": "^0.4.1",
- "graceful-fs": "^4.2.9",
+ "graceful-fs": "^4.2.11",
"json-parse-even-better-errors": "^2.3.1",
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
@@ -12212,7 +12362,7 @@
"schema-utils": "^3.2.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.10",
- "watchpack": "^2.4.0",
+ "watchpack": "^2.4.1",
"webpack-sources": "^3.2.3"
},
"bin": {
diff --git a/package.json b/package.json
index 77a58dacf5..d823ba4b30 100644
--- a/package.json
+++ b/package.json
@@ -10,10 +10,10 @@
"@citation-js/plugin-software-formats": "0.6.1",
"@claviska/jquery-minicolors": "2.3.6",
"@github/markdown-toolbar-element": "2.2.3",
- "@github/relative-time-element": "4.3.1",
+ "@github/relative-time-element": "4.4.0",
"@github/text-expander-element": "2.6.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
- "@primer/octicons": "19.8.0",
+ "@primer/octicons": "19.9.0",
"add-asset-webpack-plugin": "2.0.1",
"ansi_up": "6.0.2",
"asciinema-player": "3.7.1",
@@ -39,7 +39,7 @@
"monaco-editor": "0.47.0",
"monaco-editor-webpack-plugin": "7.1.0",
"pdfobject": "2.3.0",
- "postcss": "8.4.35",
+ "postcss": "8.4.38",
"postcss-loader": "8.1.1",
"postcss-nesting": "12.1.0",
"pretty-ms": "9.0.0",
@@ -58,7 +58,7 @@
"vue-chartjs": "5.3.0",
"vue-loader": "17.4.2",
"vue3-calendar-heatmap": "2.0.5",
- "webpack": "5.90.3",
+ "webpack": "5.91.0",
"webpack-cli": "5.1.4",
"wrap-ansi": "9.0.0"
},
@@ -69,6 +69,7 @@
"@stylistic/eslint-plugin-js": "1.7.0",
"@stylistic/stylelint-plugin": "2.1.0",
"@vitejs/plugin-vue": "5.0.4",
+ "@vue/test-utils": "2.4.5",
"eslint": "8.57.0",
"eslint-plugin-array-func": "4.0.0",
"eslint-plugin-github": "4.10.2",
@@ -76,24 +77,24 @@
"eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0",
"eslint-plugin-no-use-extend-native": "0.5.0",
- "eslint-plugin-regexp": "2.3.0",
+ "eslint-plugin-regexp": "2.4.0",
"eslint-plugin-sonarjs": "0.24.0",
"eslint-plugin-unicorn": "51.0.1",
- "eslint-plugin-vitest": "0.3.26",
- "eslint-plugin-vitest-globals": "1.4.0",
- "eslint-plugin-vue": "9.23.0",
- "eslint-plugin-vue-scoped-css": "2.7.2",
+ "eslint-plugin-vitest": "0.4.0",
+ "eslint-plugin-vitest-globals": "1.5.0",
+ "eslint-plugin-vue": "9.24.0",
+ "eslint-plugin-vue-scoped-css": "2.8.0",
"eslint-plugin-wc": "2.0.4",
- "happy-dom": "14.2.0",
+ "happy-dom": "14.3.9",
"markdownlint-cli": "0.39.0",
"postcss-html": "1.6.0",
- "stylelint": "16.2.1",
+ "stylelint": "16.3.0",
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
"stylelint-declaration-strict-value": "1.10.4",
"svgo": "3.2.0",
- "updates": "15.3.1",
+ "updates": "16.0.0",
"vite-string-plugin": "1.1.5",
- "vitest": "1.3.1"
+ "vitest": "1.4.0"
},
"browserslist": [
"defaults"
diff --git a/poetry.lock b/poetry.lock
index 46520fba3c..951a0fa7a8 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -113,18 +113,15 @@ six = ">=1.13.0"
[[package]]
name = "json5"
-version = "0.9.18"
+version = "0.9.24"
description = "A Python implementation of the JSON5 data format."
optional = false
python-versions = ">=3.8"
files = [
- {file = "json5-0.9.18-py2.py3-none-any.whl", hash = "sha256:3f20193ff8dfdec6ab114b344e7ac5d76fac453c8bab9bdfe1460d1d528ec393"},
- {file = "json5-0.9.18.tar.gz", hash = "sha256:ecb8ac357004e3522fb989da1bf08b146011edbd14fdffae6caad3bd68493467"},
+ {file = "json5-0.9.24-py3-none-any.whl", hash = "sha256:4ca101fd5c7cb47960c055ef8f4d0e31e15a7c6c48c3b6f1473fc83b6c462a13"},
+ {file = "json5-0.9.24.tar.gz", hash = "sha256:0c638399421da959a20952782800e5c1a78c14e08e1dc9738fa10d8ec14d58c8"},
]
-[package.extras]
-dev = ["hypothesis"]
-
[[package]]
name = "pathspec"
version = "0.12.1"
diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go
index b65870a8d0..8232931134 100644
--- a/routers/api/packages/generic/generic.go
+++ b/routers/api/packages/generic/generic.go
@@ -8,6 +8,7 @@ import (
"net/http"
"regexp"
"strings"
+ "unicode"
packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/log"
@@ -18,8 +19,8 @@ import (
)
var (
- packageNameRegex = regexp.MustCompile(`\A[A-Za-z0-9\.\_\-\+]+\z`)
- filenameRegex = packageNameRegex
+ packageNameRegex = regexp.MustCompile(`\A[-_+.\w]+\z`)
+ filenameRegex = regexp.MustCompile(`\A[-_+=:;.()\[\]{}~!@#$%^& \w]+\z`)
)
func apiError(ctx *context.Context, status int, obj any) {
@@ -54,20 +55,38 @@ func DownloadPackageFile(ctx *context.Context) {
helper.ServePackageFile(ctx, s, u, pf)
}
+func isValidPackageName(packageName string) bool {
+ if len(packageName) == 1 && !unicode.IsLetter(rune(packageName[0])) && !unicode.IsNumber(rune(packageName[0])) {
+ return false
+ }
+ return packageNameRegex.MatchString(packageName) && packageName != ".."
+}
+
+func isValidFileName(filename string) bool {
+ return filenameRegex.MatchString(filename) &&
+ strings.TrimSpace(filename) == filename &&
+ filename != "." && filename != ".."
+}
+
// UploadPackage uploads the specific generic package.
// Duplicated packages get rejected.
func UploadPackage(ctx *context.Context) {
packageName := ctx.Params("packagename")
filename := ctx.Params("filename")
- if !packageNameRegex.MatchString(packageName) || !filenameRegex.MatchString(filename) {
- apiError(ctx, http.StatusBadRequest, errors.New("Invalid package name or filename"))
+ if !isValidPackageName(packageName) {
+ apiError(ctx, http.StatusBadRequest, errors.New("invalid package name"))
+ return
+ }
+
+ if !isValidFileName(filename) {
+ apiError(ctx, http.StatusBadRequest, errors.New("invalid filename"))
return
}
packageVersion := ctx.Params("packageversion")
if packageVersion != strings.TrimSpace(packageVersion) {
- apiError(ctx, http.StatusBadRequest, errors.New("Invalid package version"))
+ apiError(ctx, http.StatusBadRequest, errors.New("invalid package version"))
return
}
diff --git a/routers/api/packages/generic/generic_test.go b/routers/api/packages/generic/generic_test.go
new file mode 100644
index 0000000000..1acaafe576
--- /dev/null
+++ b/routers/api/packages/generic/generic_test.go
@@ -0,0 +1,65 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package generic
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestValidatePackageName(t *testing.T) {
+ bad := []string{
+ "",
+ ".",
+ "..",
+ "-",
+ "a?b",
+ "a b",
+ "a/b",
+ }
+ for _, name := range bad {
+ assert.False(t, isValidPackageName(name), "bad=%q", name)
+ }
+
+ good := []string{
+ "a",
+ "1",
+ "a-",
+ "a_b",
+ "c.d+",
+ }
+ for _, name := range good {
+ assert.True(t, isValidPackageName(name), "good=%q", name)
+ }
+}
+
+func TestValidateFileName(t *testing.T) {
+ bad := []string{
+ "",
+ ".",
+ "..",
+ "a?b",
+ "a/b",
+ " a",
+ "a ",
+ }
+ for _, name := range bad {
+ assert.False(t, isValidFileName(name), "bad=%q", name)
+ }
+
+ good := []string{
+ "-",
+ "a",
+ "1",
+ "a-",
+ "a_b",
+ "a b",
+ "c.d+",
+ `-_+=:;.()[]{}~!@#$%^& aA1`,
+ }
+ for _, name := range good {
+ assert.True(t, isValidFileName(name), "good=%q", name)
+ }
+}
diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go
index 72ada25ec7..712cba7455 100644
--- a/routers/api/v1/repo/file.go
+++ b/routers/api/v1/repo/file.go
@@ -150,7 +150,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
return
}
- // OK, now the blob is known to have at most 1024 bytes we can simply read this in in one go (This saves reading it twice)
+ // OK, now the blob is known to have at most 1024 bytes we can simply read this in one go (This saves reading it twice)
dataRc, err := blob.DataAsync()
if err != nil {
ctx.ServerError("DataAsync", err)
diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go
index 9ccbb57c52..77c0d25e2a 100644
--- a/routers/api/v1/repo/pull_review.go
+++ b/routers/api/v1/repo/pull_review.go
@@ -787,6 +787,8 @@ func DeleteReviewRequests(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
// "422":
// "$ref": "#/responses/validationError"
+ // "403":
+ // "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
opts := web.GetForm(ctx).(*api.PullReviewRequestOptions)
@@ -855,6 +857,10 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions
for _, reviewer := range reviewers {
comment, err := issue_service.ReviewRequest(ctx, pr.Issue, ctx.Doer, reviewer, isAdd)
if err != nil {
+ if issues_model.IsErrReviewRequestOnClosedPR(err) {
+ ctx.Error(http.StatusForbidden, "", err)
+ return
+ }
ctx.Error(http.StatusInternalServerError, "ReviewRequest", err)
return
}
@@ -1068,7 +1074,7 @@ func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors
ctx.Error(http.StatusForbidden, "", "Must be repo admin")
return
}
- review, pr, isWrong := prepareSingleReview(ctx)
+ review, _, isWrong := prepareSingleReview(ctx)
if isWrong {
return
}
@@ -1078,13 +1084,12 @@ func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors
return
}
- if pr.Issue.IsClosed {
- ctx.Error(http.StatusForbidden, "", "not need to dismiss this review because this pr is closed")
- return
- }
-
_, err := pull_service.DismissReview(ctx, review.ID, ctx.Repo.Repository.ID, msg, ctx.Doer, isDismiss, dismissPriors)
if err != nil {
+ if pull_service.IsErrDismissRequestOnClosedPR(err) {
+ ctx.Error(http.StatusForbidden, "", err)
+ return
+ }
ctx.Error(http.StatusInternalServerError, "pull_service.DismissReview", err)
return
}
diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go
index ad8bb90d9e..db1dac0120 100644
--- a/routers/web/org/projects.go
+++ b/routers/web/org/projects.go
@@ -207,11 +207,7 @@ func ChangeProjectStatus(ctx *context.Context) {
id := ctx.ParamsInt64(":id")
if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, 0, id, toClose); err != nil {
- if project_model.IsErrProjectNotExist(err) {
- ctx.NotFound("", err)
- } else {
- ctx.ServerError("ChangeProjectStatusByRepoIDAndID", err)
- }
+ ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
return
}
ctx.Redirect(ctx.ContextUser.HomeLink() + "/-/projects?state=" + url.QueryEscape(ctx.Params(":action")))
@@ -221,11 +217,7 @@ func ChangeProjectStatus(ctx *context.Context) {
func DeleteProject(ctx *context.Context) {
p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
- if project_model.IsErrProjectNotExist(err) {
- ctx.NotFound("", nil)
- } else {
- ctx.ServerError("GetProjectByID", err)
- }
+ ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
if p.OwnerID != ctx.ContextUser.ID {
@@ -254,11 +246,7 @@ func RenderEditProject(ctx *context.Context) {
p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
- if project_model.IsErrProjectNotExist(err) {
- ctx.NotFound("", nil)
- } else {
- ctx.ServerError("GetProjectByID", err)
- }
+ ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
if p.OwnerID != ctx.ContextUser.ID {
@@ -303,11 +291,7 @@ func EditProjectPost(ctx *context.Context) {
p, err := project_model.GetProjectByID(ctx, projectID)
if err != nil {
- if project_model.IsErrProjectNotExist(err) {
- ctx.NotFound("", nil)
- } else {
- ctx.ServerError("GetProjectByID", err)
- }
+ ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
if p.OwnerID != ctx.ContextUser.ID {
@@ -335,11 +319,7 @@ func EditProjectPost(ctx *context.Context) {
func ViewProject(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
- if project_model.IsErrProjectNotExist(err) {
- ctx.NotFound("", nil)
- } else {
- ctx.ServerError("GetProjectByID", err)
- }
+ ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
if project.OwnerID != ctx.ContextUser.ID {
@@ -353,10 +333,6 @@ func ViewProject(ctx *context.Context) {
return
}
- if boards[0].ID == 0 {
- boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized")
- }
-
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
if err != nil {
ctx.ServerError("LoadIssuesOfBoards", err)
@@ -493,11 +469,7 @@ func DeleteProjectBoard(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
- if project_model.IsErrProjectNotExist(err) {
- ctx.NotFound("", nil)
- } else {
- ctx.ServerError("GetProjectByID", err)
- }
+ ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
@@ -534,11 +506,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
- if project_model.IsErrProjectNotExist(err) {
- ctx.NotFound("", nil)
- } else {
- ctx.ServerError("GetProjectByID", err)
- }
+ ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
@@ -566,11 +534,7 @@ func CheckProjectBoardChangePermissions(ctx *context.Context) (*project_model.Pr
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
- if project_model.IsErrProjectNotExist(err) {
- ctx.NotFound("", nil)
- } else {
- ctx.ServerError("GetProjectByID", err)
- }
+ ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return nil, nil
}
@@ -636,21 +600,6 @@ func SetDefaultProjectBoard(ctx *context.Context) {
ctx.JSONOK()
}
-// UnsetDefaultProjectBoard unset default board for uncategorized issues/pulls
-func UnsetDefaultProjectBoard(ctx *context.Context) {
- project, _ := CheckProjectBoardChangePermissions(ctx)
- if ctx.Written() {
- return
- }
-
- if err := project_model.SetDefaultBoard(ctx, project.ID, 0); err != nil {
- ctx.ServerError("SetDefaultBoard", err)
- return
- }
-
- ctx.JSONOK()
-}
-
// MoveIssues moves or keeps issues in a column and sorts them inside that column
func MoveIssues(ctx *context.Context) {
if ctx.Doer == nil {
@@ -662,11 +611,7 @@ func MoveIssues(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
- if project_model.IsErrProjectNotExist(err) {
- ctx.NotFound("ProjectNotExist", nil)
- } else {
- ctx.ServerError("GetProjectByID", err)
- }
+ ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
if project.OwnerID != ctx.ContextUser.ID {
@@ -674,28 +619,15 @@ func MoveIssues(ctx *context.Context) {
return
}
- var board *project_model.Board
+ board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
+ if err != nil {
+ ctx.NotFoundOrServerError("GetProjectBoard", project_model.IsErrProjectBoardNotExist, err)
+ return
+ }
- if ctx.ParamsInt64(":boardID") == 0 {
- board = &project_model.Board{
- ID: 0,
- ProjectID: project.ID,
- Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
- }
- } else {
- board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
- if err != nil {
- if project_model.IsErrProjectBoardNotExist(err) {
- ctx.NotFound("ProjectBoardNotExist", nil)
- } else {
- ctx.ServerError("GetProjectBoard", err)
- }
- return
- }
- if board.ProjectID != project.ID {
- ctx.NotFound("BoardNotInProject", nil)
- return
- }
+ if board.ProjectID != project.ID {
+ ctx.NotFound("BoardNotInProject", nil)
+ return
}
type movedIssuesForm struct {
@@ -718,11 +650,7 @@ func MoveIssues(ctx *context.Context) {
}
movedIssues, err := issues_model.GetIssuesByIDs(ctx, issueIDs)
if err != nil {
- if issues_model.IsErrIssueNotExist(err) {
- ctx.NotFound("IssueNotExisting", nil)
- } else {
- ctx.ServerError("GetIssueByID", err)
- }
+ ctx.NotFoundOrServerError("GetIssueByID", issues_model.IsErrIssueNotExist, err)
return
}
diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go
index 494ada4323..0be734abaf 100644
--- a/routers/web/org/setting.go
+++ b/routers/web/org/setting.go
@@ -26,6 +26,7 @@ import (
org_service "code.gitea.io/gitea/services/org"
repo_service "code.gitea.io/gitea/services/repository"
user_service "code.gitea.io/gitea/services/user"
+ webhook_service "code.gitea.io/gitea/services/webhook"
)
const (
@@ -210,6 +211,7 @@ func Webhooks(ctx *context.Context) {
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["BaseLink"] = ctx.Org.OrgLink + "/settings/hooks"
ctx.Data["BaseLinkNew"] = ctx.Org.OrgLink + "/settings/hooks"
+ ctx.Data["WebhookList"] = webhook_service.List()
ctx.Data["Description"] = ctx.Tr("org.settings.hooks_desc")
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{OwnerID: ctx.Org.Organization.ID})
diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go
index 0f464ad076..eea3d4dc00 100644
--- a/routers/web/repo/blame.go
+++ b/routers/web/repo/blame.go
@@ -258,9 +258,9 @@ func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames
var avatar string
if commit.User != nil {
- avatar = string(avatarUtils.Avatar(commit.User, 18, "gt-mr-3"))
+ avatar = string(avatarUtils.Avatar(commit.User, 18))
} else {
- avatar = string(avatarUtils.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18, "gt-mr-3"))
+ avatar = string(avatarUtils.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18, "tw-mr-2"))
}
br.Avatar = gotemplate.HTML(avatar)
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index c0ec121336..a4e3f7165a 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -1600,7 +1600,7 @@ func ViewIssue(ctx *context.Context) {
}
marked[issue.PosterID] = issue.ShowRole
- // Render comments and and fetch participants.
+ // Render comments and fetch participants.
participants[0] = issue.Poster
for _, comment = range issue.Comments {
comment.Issue = issue
@@ -2494,6 +2494,10 @@ func UpdatePullReviewRequest(ctx *context.Context) {
_, err = issue_service.ReviewRequest(ctx, issue, ctx.Doer, reviewer, action == "attach")
if err != nil {
+ if issues_model.IsErrReviewRequestOnClosedPR(err) {
+ ctx.Status(http.StatusForbidden)
+ return
+ }
ctx.ServerError("ReviewRequest", err)
return
}
diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go
index dfee2863b5..c817d6aa96 100644
--- a/routers/web/repo/issue_content_history.go
+++ b/routers/web/repo/issue_content_history.go
@@ -70,7 +70,7 @@ func GetContentHistoryList(ctx *context.Context) {
}
src := html.EscapeString(item.UserAvatarLink)
- class := avatars.DefaultAvatarClass + " gt-mr-3"
+ class := avatars.DefaultAvatarClass + " tw-mr-2"
name := html.EscapeString(username)
avatarHTML := string(templates.AvatarHTML(src, 28, class, username))
timeSinceText := string(timeutil.TimeSinceUnix(item.EditedUnix, ctx.Locale))
diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go
index 4c171defbd..aa42585590 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -314,10 +314,6 @@ func ViewProject(ctx *context.Context) {
return
}
- if boards[0].ID == 0 {
- boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized")
- }
-
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
if err != nil {
ctx.ServerError("LoadIssuesOfBoards", err)
@@ -582,21 +578,6 @@ func SetDefaultProjectBoard(ctx *context.Context) {
ctx.JSONOK()
}
-// UnSetDefaultProjectBoard unset default board for uncategorized issues/pulls
-func UnSetDefaultProjectBoard(ctx *context.Context) {
- project, _ := checkProjectBoardChangePermissions(ctx)
- if ctx.Written() {
- return
- }
-
- if err := project_model.SetDefaultBoard(ctx, project.ID, 0); err != nil {
- ctx.ServerError("SetDefaultBoard", err)
- return
- }
-
- ctx.JSONOK()
-}
-
// MoveIssues moves or keeps issues in a column and sorts them inside that column
func MoveIssues(ctx *context.Context) {
if ctx.Doer == nil {
@@ -627,28 +608,19 @@ func MoveIssues(ctx *context.Context) {
return
}
- var board *project_model.Board
-
- if ctx.ParamsInt64(":boardID") == 0 {
- board = &project_model.Board{
- ID: 0,
- ProjectID: project.ID,
- Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
- }
- } else {
- board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
- if err != nil {
- if project_model.IsErrProjectBoardNotExist(err) {
- ctx.NotFound("ProjectBoardNotExist", nil)
- } else {
- ctx.ServerError("GetProjectBoard", err)
- }
- return
- }
- if board.ProjectID != project.ID {
- ctx.NotFound("BoardNotInProject", nil)
- return
+ board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
+ if err != nil {
+ if project_model.IsErrProjectBoardNotExist(err) {
+ ctx.NotFound("ProjectBoardNotExist", nil)
+ } else {
+ ctx.ServerError("GetProjectBoard", err)
}
+ return
+ }
+
+ if board.ProjectID != project.ID {
+ ctx.NotFound("BoardNotInProject", nil)
+ return
}
type movedIssuesForm struct {
diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go
index ead7592fd0..afa3b17d31 100644
--- a/routers/web/repo/pull_review.go
+++ b/routers/web/repo/pull_review.go
@@ -263,6 +263,10 @@ func DismissReview(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.DismissReviewForm)
comm, err := pull_service.DismissReview(ctx, form.ReviewID, ctx.Repo.Repository.ID, form.Message, ctx.Doer, true, true)
if err != nil {
+ if pull_service.IsErrDismissRequestOnClosedPR(err) {
+ ctx.Status(http.StatusForbidden)
+ return
+ }
ctx.ServerError("pull_service.DismissReview", err)
return
}
diff --git a/routers/web/repo/pull_review_test.go b/routers/web/repo/pull_review_test.go
index d87656f796..70f6a0e055 100644
--- a/routers/web/repo/pull_review_test.go
+++ b/routers/web/repo/pull_review_test.go
@@ -4,6 +4,7 @@
package repo
import (
+ "net/http"
"net/http/httptest"
"testing"
@@ -75,4 +76,30 @@ func TestRenderConversation(t *testing.T) {
renderConversation(ctx, preparedComment, "timeline")
assert.Contains(t, resp.Body.String(), `<div id="code-comments-`)
})
+ run("diff non-existing review", func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder) {
+ reviews, err := issues_model.FindReviews(db.DefaultContext, issues_model.FindReviewOptions{
+ IssueID: 2,
+ })
+ assert.NoError(t, err)
+ for _, r := range reviews {
+ assert.NoError(t, issues_model.DeleteReview(db.DefaultContext, r))
+ }
+ ctx.Data["ShowOutdatedComments"] = true
+ renderConversation(ctx, preparedComment, "diff")
+ assert.Equal(t, http.StatusOK, resp.Code)
+ assert.NotContains(t, resp.Body.String(), `status-page-500`)
+ })
+ run("timeline non-existing review", func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder) {
+ reviews, err := issues_model.FindReviews(db.DefaultContext, issues_model.FindReviewOptions{
+ IssueID: 2,
+ })
+ assert.NoError(t, err)
+ for _, r := range reviews {
+ assert.NoError(t, issues_model.DeleteReview(db.DefaultContext, r))
+ }
+ ctx.Data["ShowOutdatedComments"] = true
+ renderConversation(ctx, preparedComment, "timeline")
+ assert.Equal(t, http.StatusOK, resp.Code)
+ assert.NotContains(t, resp.Body.String(), `status-page-500`)
+ })
}
diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go
index 38bb1305fb..54e9aed207 100644
--- a/routers/web/repo/release.go
+++ b/routers/web/repo/release.go
@@ -11,6 +11,7 @@ import (
"strings"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
@@ -18,6 +19,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
@@ -192,6 +194,7 @@ func Releases(ctx *context.Context) {
}
ctx.Data["Releases"] = releases
+ addVerifyTagToContext(ctx)
numReleases := ctx.Data["NumReleases"].(int64)
pager := context.NewPagination(int(numReleases), listOptions.PageSize, listOptions.Page, 5)
@@ -201,6 +204,44 @@ func Releases(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplReleasesList)
}
+func verifyTagSignature(ctx *context.Context, r *repo_model.Release) (*asymkey.ObjectVerification, error) {
+ if err := r.LoadAttributes(ctx); err != nil {
+ return nil, err
+ }
+ gitRepo, err := gitrepo.OpenRepository(ctx, r.Repo)
+ if err != nil {
+ return nil, err
+ }
+ defer gitRepo.Close()
+
+ tag, err := gitRepo.GetTag(r.TagName)
+ if err != nil {
+ return nil, err
+ }
+ if tag.Signature == nil {
+ return nil, nil
+ }
+
+ verification := asymkey.ParseTagWithSignature(ctx, gitRepo, tag)
+ return verification, nil
+}
+
+func addVerifyTagToContext(ctx *context.Context) {
+ ctx.Data["VerifyTag"] = func(r *repo_model.Release) *asymkey.ObjectVerification {
+ v, err := verifyTagSignature(ctx, r)
+ if err != nil {
+ return nil
+ }
+ return v
+ }
+ ctx.Data["HasSignature"] = func(verification *asymkey.ObjectVerification) bool {
+ if verification == nil {
+ return false
+ }
+ return verification.Reason != "gpg.error.not_signed_commit"
+ }
+}
+
// TagsList render tags list page
func TagsList(ctx *context.Context) {
ctx.Data["PageIsTagList"] = true
@@ -240,6 +281,7 @@ func TagsList(ctx *context.Context) {
}
ctx.Data["Releases"] = releases
+ addVerifyTagToContext(ctx)
numTags := ctx.Data["NumTags"].(int64)
pager := context.NewPagination(int(numTags), opts.PageSize, opts.Page, 5)
@@ -304,6 +346,7 @@ func SingleRelease(ctx *context.Context) {
if release.IsTag && release.Title == "" {
release.Title = release.TagName
}
+ addVerifyTagToContext(ctx)
ctx.Data["PageIsSingleTag"] = release.IsTag
if release.IsTag {
diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go
index 10fa21c60e..e64db03e20 100644
--- a/routers/web/repo/render.go
+++ b/routers/web/repo/render.go
@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
@@ -44,20 +45,17 @@ func RenderFile(ctx *context.Context) {
isTextFile := st.IsText()
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
+ ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts")
if markupType := markup.Type(blob.Name()); markupType == "" {
if isTextFile {
- _, err = io.Copy(ctx.Resp, rd)
- if err != nil {
- ctx.ServerError("Copy", err)
- }
- return
+ _, _ = io.Copy(ctx.Resp, rd)
+ } else {
+ http.Error(ctx.Resp, "Unsupported file type render", http.StatusInternalServerError)
}
- ctx.Error(http.StatusInternalServerError, "Unsupported file type render")
return
}
- ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts")
err = markup.Render(&markup.RenderContext{
Ctx: ctx,
RelativePath: ctx.Repo.TreePath,
@@ -71,7 +69,8 @@ func RenderFile(ctx *context.Context) {
InStandalonePage: true,
}, rd, ctx.Resp)
if err != nil {
- ctx.ServerError("Render", err)
+ log.Error("Failed to render file %q: %v", ctx.Repo.TreePath, err)
+ http.Error(ctx.Resp, "Failed to render file", http.StatusInternalServerError)
return
}
}
diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go
index bf7770bfdb..4469eac9e8 100644
--- a/routers/web/repo/setting/webhook.go
+++ b/routers/web/repo/setting/webhook.go
@@ -45,6 +45,7 @@ func WebhookList(ctx *context.Context) {
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["BaseLink"] = ctx.Repo.RepoLink + "/settings/hooks"
ctx.Data["BaseLinkNew"] = ctx.Repo.RepoLink + "/settings/hooks"
+ ctx.Data["WebhookList"] = webhook_service.List()
ctx.Data["Description"] = ctx.Tr("repo.settings.hooks_desc", "https://forgejo.org/docs/latest/user/webhooks/")
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{RepoID: ctx.Repo.Repository.ID})
@@ -132,13 +133,16 @@ func WebhookNew(ctx *context.Context) {
}
hookType := ctx.Params(":type")
- if webhook_service.GetWebhookHandler(hookType) == nil {
+ handler := webhook_service.GetWebhookHandler(hookType)
+ if handler == nil {
ctx.NotFound("GetWebhookHandler", nil)
return
}
ctx.Data["HookType"] = hookType
+ ctx.Data["WebhookHandler"] = handler
ctx.Data["BaseLink"] = orCtx.LinkNew
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
+ ctx.Data["WebhookList"] = webhook_service.List()
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
}
@@ -194,6 +198,7 @@ func WebhookCreate(ctx *context.Context) {
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}}
ctx.Data["HookType"] = hookType
+ ctx.Data["WebhookHandler"] = handler
orCtx, err := getOwnerRepoCtx(ctx)
if err != nil {
@@ -202,6 +207,7 @@ func WebhookCreate(ctx *context.Context) {
}
ctx.Data["BaseLink"] = orCtx.LinkNew
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
+ ctx.Data["WebhookList"] = webhook_service.List()
if ctx.HasError() {
// pre-fill the form with the submitted data
@@ -336,6 +342,7 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
}
ctx.Data["BaseLink"] = orCtx.Link
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
+ ctx.Data["WebhookList"] = webhook_service.List()
var w *webhook.Webhook
if orCtx.RepoID > 0 {
@@ -358,6 +365,7 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
if handler := webhook_service.GetWebhookHandler(w.Type); handler != nil {
ctx.Data["HookMetadata"] = handler.Metadata(w)
+ ctx.Data["WebhookHandler"] = handler
}
ctx.Data["History"], err = w.History(ctx, 1)
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index becb8748cd..a65a100212 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -938,9 +938,9 @@ func prepareOpenWithEditorApps(ctx *context.Context) {
schema, _, _ := strings.Cut(app.OpenURL, ":")
var iconHTML template.HTML
if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" {
- iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-open-with-%s", schema), 16, "gt-mr-3")
+ iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-open-with-%s", schema), 16, "tw-mr-2")
} else {
- iconHTML = svg.RenderHTML("gitea-git", 16, "gt-mr-3") // TODO: it could support user's customized icon in the future
+ iconHTML = svg.RenderHTML("gitea-git", 16, "tw-mr-2") // TODO: it could support user's customized icon in the future
}
tmplApps = append(tmplApps, map[string]any{
"DisplayName": app.DisplayName,
diff --git a/routers/web/user/setting/webhooks.go b/routers/web/user/setting/webhooks.go
index 4423b62781..3cc67d9def 100644
--- a/routers/web/user/setting/webhooks.go
+++ b/routers/web/user/setting/webhooks.go
@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context"
+ webhook_service "code.gitea.io/gitea/services/webhook"
)
const (
@@ -23,6 +24,7 @@ func Webhooks(ctx *context.Context) {
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["BaseLink"] = setting.AppSubURL + "/user/settings/hooks"
ctx.Data["BaseLinkNew"] = setting.AppSubURL + "/user/settings/hooks"
+ ctx.Data["WebhookList"] = webhook_service.List()
ctx.Data["Description"] = ctx.Tr("settings.hooks.desc")
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{OwnerID: ctx.Doer.ID})
diff --git a/routers/web/web.go b/routers/web/web.go
index 7329acd155..40f4ffc018 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -986,7 +986,6 @@ func registerRoutes(m *web.Route) {
m.Put("", web.Bind(forms.EditProjectBoardForm{}), org.EditProjectBoard)
m.Delete("", org.DeleteProjectBoard)
m.Post("/default", org.SetDefaultProjectBoard)
- m.Post("/unsetdefault", org.UnsetDefaultProjectBoard)
m.Post("/move", org.MoveIssues)
})
@@ -1360,7 +1359,6 @@ func registerRoutes(m *web.Route) {
m.Put("", web.Bind(forms.EditProjectBoardForm{}), repo.EditProjectBoard)
m.Delete("", repo.DeleteProjectBoard)
m.Post("/default", repo.SetDefaultProjectBoard)
- m.Post("/unsetdefault", repo.UnSetDefaultProjectBoard)
m.Post("/move", repo.MoveIssues)
})
diff --git a/services/auth/source/oauth2/providers.go b/services/auth/source/oauth2/providers.go
index c3edae4ab6..6ed6c184eb 100644
--- a/services/auth/source/oauth2/providers.go
+++ b/services/auth/source/oauth2/providers.go
@@ -59,7 +59,7 @@ func (p *AuthSourceProvider) DisplayName() string {
func (p *AuthSourceProvider) IconHTML(size int) template.HTML {
if p.iconURL != "" {
- img := fmt.Sprintf(`<img class="tw-object-contain gt-mr-3" width="%d" height="%d" src="%s" alt="%s">`,
+ img := fmt.Sprintf(`<img class="tw-object-contain tw-mr-2" width="%d" height="%d" src="%s" alt="%s">`,
size,
size,
html.EscapeString(p.iconURL), html.EscapeString(p.DisplayName()),
diff --git a/services/auth/source/oauth2/providers_base.go b/services/auth/source/oauth2/providers_base.go
index 5b6694487b..9d4ab106e5 100644
--- a/services/auth/source/oauth2/providers_base.go
+++ b/services/auth/source/oauth2/providers_base.go
@@ -35,10 +35,10 @@ func (b *BaseProvider) IconHTML(size int) template.HTML {
case "github":
svgName = "octicon-mark-github"
}
- svgHTML := svg.RenderHTML(svgName, size, "gt-mr-3")
+ svgHTML := svg.RenderHTML(svgName, size, "tw-mr-2")
if svgHTML == "" {
log.Error("No SVG icon for oauth2 provider %q", b.name)
- svgHTML = svg.RenderHTML("gitea-openid", size, "gt-mr-3")
+ svgHTML = svg.RenderHTML("gitea-openid", size, "tw-mr-2")
}
return svgHTML
}
diff --git a/services/auth/source/oauth2/providers_openid.go b/services/auth/source/oauth2/providers_openid.go
index a4dcfcafc7..285876d5ac 100644
--- a/services/auth/source/oauth2/providers_openid.go
+++ b/services/auth/source/oauth2/providers_openid.go
@@ -29,7 +29,7 @@ func (o *OpenIDProvider) DisplayName() string {
// IconHTML returns icon HTML for this provider
func (o *OpenIDProvider) IconHTML(size int) template.HTML {
- return svg.RenderHTML("gitea-openid", size, "gt-mr-3")
+ return svg.RenderHTML("gitea-openid", size, "tw-mr-2")
}
// CreateGothProvider creates a GothProvider from this Provider
diff --git a/services/doctor/fix16961.go b/services/doctor/fix16961.go
index d3f36d8d5c..50d9ac6621 100644
--- a/services/doctor/fix16961.go
+++ b/services/doctor/fix16961.go
@@ -216,6 +216,12 @@ func fixBrokenRepoUnit16961(repoUnit *repo_model.RepoUnit, bs []byte) (fixed boo
return false, nil
}
+ var cfg any
+ err = json.UnmarshalHandleDoubleEncode(bs, &cfg)
+ if err == nil {
+ return false, nil
+ }
+
switch repoUnit.Type {
case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects:
cfg := &repo_model.UnitConfig{}
diff --git a/services/doctor/storage.go b/services/doctor/storage.go
index f338537864..787df27549 100644
--- a/services/doctor/storage.go
+++ b/services/doctor/storage.go
@@ -162,7 +162,7 @@ func checkStorage(opts *checkStorageOptions) func(ctx context.Context, logger lo
if opts.RepoArchives || opts.All {
if err := commonCheckStorage(ctx, logger, autofix,
&commonStorageCheckOptions{
- storer: storage.RepoAvatars,
+ storer: storage.RepoArchives,
isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
exists, err := repo.ExistsRepoArchiverWithStoragePath(ctx, path)
if err == nil || errors.Is(err, util.ErrInvalidArgument) {
diff --git a/services/issue/assignee.go b/services/issue/assignee.go
index b5f472ba53..8740a6664a 100644
--- a/services/issue/assignee.go
+++ b/services/issue/assignee.go
@@ -226,16 +226,33 @@ func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *use
return nil, nil
}
+ return comment, teamReviewRequestNotify(ctx, issue, doer, reviewer, isAdd, comment)
+}
+
+func ReviewRequestNotify(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewNotifers []*ReviewRequestNotifier) {
+ for _, reviewNotifer := range reviewNotifers {
+ if reviewNotifer.Reviwer != nil {
+ notify_service.PullRequestReviewRequest(ctx, issue.Poster, issue, reviewNotifer.Reviwer, reviewNotifer.IsAdd, reviewNotifer.Comment)
+ } else if reviewNotifer.ReviewTeam != nil {
+ if err := teamReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifer.ReviewTeam, reviewNotifer.IsAdd, reviewNotifer.Comment); err != nil {
+ log.Error("teamReviewRequestNotify: %v", err)
+ }
+ }
+ }
+}
+
+// teamReviewRequestNotify notify all user in this team
+func teamReviewRequestNotify(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewer *organization.Team, isAdd bool, comment *issues_model.Comment) error {
// notify all user in this team
if err := comment.LoadIssue(ctx); err != nil {
- return nil, err
+ return err
}
members, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{
TeamID: reviewer.ID,
})
if err != nil {
- return nil, err
+ return err
}
for _, member := range members {
@@ -246,7 +263,7 @@ func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *use
notify_service.PullRequestReviewRequest(ctx, doer, issue, member, isAdd, comment)
}
- return comment, err
+ return err
}
// CanDoerChangeReviewRequests returns if the doer can add/remove review requests of a PR
diff --git a/services/issue/issue.go b/services/issue/issue.go
index 627c6d4bce..5e726176d0 100644
--- a/services/issue/issue.go
+++ b/services/issue/issue.go
@@ -17,6 +17,7 @@ import (
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/timeutil"
notify_service "code.gitea.io/gitea/services/notify"
@@ -64,17 +65,29 @@ func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_mode
return nil
}
+ if err := issue.LoadRepo(ctx); err != nil {
+ return err
+ }
+
+ if user_model.IsBlockedMultiple(ctx, []int64{issue.PosterID, issue.Repo.OwnerID}, doer.ID) {
+ return user_model.ErrBlockedByUser
+ }
+
if err := issues_model.ChangeIssueTitle(ctx, issue, doer, oldTitle); err != nil {
return err
}
+ var reviewNotifers []*ReviewRequestNotifier
if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issues_model.HasWorkInProgressPrefix(title) {
- if err := issues_model.PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest); err != nil {
- return err
+ var err error
+ reviewNotifers, err = PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest)
+ if err != nil {
+ log.Error("PullRequestCodeOwnersReview: %v", err)
}
}
notify_service.IssueChangeTitle(ctx, doer, issue, oldTitle)
+ ReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifers)
return nil
}
diff --git a/services/issue/pull.go b/services/issue/pull.go
new file mode 100644
index 0000000000..b7b63a7024
--- /dev/null
+++ b/services/issue/pull.go
@@ -0,0 +1,147 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package issue
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ issues_model "code.gitea.io/gitea/models/issues"
+ org_model "code.gitea.io/gitea/models/organization"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+)
+
+func getMergeBase(repo *git.Repository, pr *issues_model.PullRequest, baseBranch, headBranch string) (string, error) {
+ // Add a temporary remote
+ tmpRemote := fmt.Sprintf("mergebase-%d-%d", pr.ID, time.Now().UnixNano())
+ if err := repo.AddRemote(tmpRemote, repo.Path, false); err != nil {
+ return "", fmt.Errorf("AddRemote: %w", err)
+ }
+ defer func() {
+ if err := repo.RemoveRemote(tmpRemote); err != nil {
+ log.Error("getMergeBase: RemoveRemote: %v", err)
+ }
+ }()
+
+ mergeBase, _, err := repo.GetMergeBase(tmpRemote, baseBranch, headBranch)
+ return mergeBase, err
+}
+
+type ReviewRequestNotifier struct {
+ Comment *issues_model.Comment
+ IsAdd bool
+ Reviwer *user_model.User
+ ReviewTeam *org_model.Team
+}
+
+func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue, pr *issues_model.PullRequest) ([]*ReviewRequestNotifier, error) {
+ files := []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}
+
+ if pr.IsWorkInProgress(ctx) {
+ return nil, nil
+ }
+
+ if err := pr.LoadHeadRepo(ctx); err != nil {
+ return nil, err
+ }
+
+ if pr.HeadRepo.IsFork {
+ return nil, nil
+ }
+
+ if err := pr.LoadBaseRepo(ctx); err != nil {
+ return nil, err
+ }
+
+ repo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
+ if err != nil {
+ return nil, err
+ }
+ defer repo.Close()
+
+ commit, err := repo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
+ if err != nil {
+ return nil, err
+ }
+
+ var data string
+ for _, file := range files {
+ if blob, err := commit.GetBlobByPath(file); err == nil {
+ data, err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
+ if err == nil {
+ break
+ }
+ }
+ }
+
+ rules, _ := issues_model.GetCodeOwnersFromContent(ctx, data)
+
+ // get the mergebase
+ mergeBase, err := getMergeBase(repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
+ if err != nil {
+ return nil, err
+ }
+
+ // https://github.com/go-gitea/gitea/issues/29763, we need to get the files changed
+ // between the merge base and the head commit but not the base branch and the head commit
+ changedFiles, err := repo.GetFilesChangedBetween(mergeBase, pr.GetGitRefName())
+ if err != nil {
+ return nil, err
+ }
+
+ uniqUsers := make(map[int64]*user_model.User)
+ uniqTeams := make(map[string]*org_model.Team)
+ for _, rule := range rules {
+ for _, f := range changedFiles {
+ if (rule.Rule.MatchString(f) && !rule.Negative) || (!rule.Rule.MatchString(f) && rule.Negative) {
+ for _, u := range rule.Users {
+ uniqUsers[u.ID] = u
+ }
+ for _, t := range rule.Teams {
+ uniqTeams[fmt.Sprintf("%d/%d", t.OrgID, t.ID)] = t
+ }
+ }
+ }
+ }
+
+ notifiers := make([]*ReviewRequestNotifier, 0, len(uniqUsers)+len(uniqTeams))
+
+ if err := issue.LoadPoster(ctx); err != nil {
+ return nil, err
+ }
+
+ for _, u := range uniqUsers {
+ if u.ID != issue.Poster.ID {
+ comment, err := issues_model.AddReviewRequest(ctx, issue, u, issue.Poster)
+ if err != nil {
+ log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err)
+ return nil, err
+ }
+ notifiers = append(notifiers, &ReviewRequestNotifier{
+ Comment: comment,
+ IsAdd: true,
+ Reviwer: u,
+ })
+ }
+ }
+ for _, t := range uniqTeams {
+ comment, err := issues_model.AddTeamReviewRequest(ctx, issue, t, issue.Poster)
+ if err != nil {
+ log.Warn("Failed add assignee team: %s to PR review: %s#%d, error: %s", t.Name, pr.BaseRepo.Name, pr.ID, err)
+ return nil, err
+ }
+ notifiers = append(notifiers, &ReviewRequestNotifier{
+ Comment: comment,
+ IsAdd: true,
+ ReviewTeam: t,
+ })
+ }
+
+ return notifiers, nil
+}
diff --git a/services/markup/processorhelper.go b/services/markup/processorhelper.go
index a4378678a0..40bf1d65da 100644
--- a/services/markup/processorhelper.go
+++ b/services/markup/processorhelper.go
@@ -5,10 +5,18 @@ package markup
import (
"context"
+ "fmt"
+ "code.gitea.io/gitea/models/perm/access"
+ "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
gitea_context "code.gitea.io/gitea/services/context"
+ file_service "code.gitea.io/gitea/services/repository/files"
)
func ProcessorHelper() *markup.ProcessorHelper {
@@ -29,5 +37,51 @@ func ProcessorHelper() *markup.ProcessorHelper {
// when using gitea context (web context), use user's visibility and user's permission to check
return user.IsUserVisibleToViewer(giteaCtx, mentionedUser, giteaCtx.Doer)
},
+ GetRepoFileBlob: func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error) {
+ repo, err := repo.GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
+ if err != nil {
+ return nil, err
+ }
+
+ var user *user.User
+
+ giteaCtx, ok := ctx.(*gitea_context.Context)
+ if ok {
+ user = giteaCtx.Doer
+ }
+
+ perms, err := access.GetUserRepoPermission(ctx, repo, user)
+ if err != nil {
+ return nil, err
+ }
+ if !perms.CanRead(unit.TypeCode) {
+ return nil, fmt.Errorf("cannot access repository code")
+ }
+
+ gitRepo, err := gitrepo.OpenRepository(ctx, repo)
+ if err != nil {
+ return nil, err
+ }
+ defer gitRepo.Close()
+
+ commit, err := gitRepo.GetCommit(commitSha)
+ if err != nil {
+ return nil, err
+ }
+
+ if language != nil {
+ *language, err = file_service.TryGetContentLanguage(gitRepo, commitSha, filePath)
+ if err != nil {
+ log.Error("Unable to get file language for %-v:%s. Error: %v", repo, filePath, err)
+ }
+ }
+
+ blob, err := commit.GetBlobByPath(filePath)
+ if err != nil {
+ return nil, err
+ }
+
+ return blob, nil
+ },
}
}
diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go
index ae164a7add..367818c0e7 100644
--- a/services/migrations/migrate.go
+++ b/services/migrations/migrate.go
@@ -257,14 +257,13 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba
}
log.Warn("migrating milestones is not supported, ignored")
}
-
msBatchSize := uploader.MaxBatchInsertSize("milestone")
for len(milestones) > 0 {
if len(milestones) < msBatchSize {
msBatchSize = len(milestones)
}
- if err := uploader.CreateMilestones(milestones...); err != nil {
+ if err := uploader.CreateMilestones(milestones[:msBatchSize]...); err != nil {
return err
}
milestones = milestones[msBatchSize:]
diff --git a/services/pull/pull.go b/services/pull/pull.go
index 5d597b7a77..fb4b88309a 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -74,6 +74,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss
}
defer baseGitRepo.Close()
+ var reviewNotifers []*issue_service.ReviewRequestNotifier
if err := db.WithTx(ctx, func(ctx context.Context) error {
if err := issues_model.NewPullRequest(ctx, repo, issue, labelIDs, uuids, pr); err != nil {
return err
@@ -133,7 +134,8 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss
}
if !pr.IsWorkInProgress(ctx) {
- if err := issues_model.PullRequestCodeOwnersReview(ctx, issue, pr); err != nil {
+ reviewNotifers, err = issue_service.PullRequestCodeOwnersReview(ctx, issue, pr)
+ if err != nil {
return err
}
}
@@ -147,11 +149,12 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss
}
baseGitRepo.Close() // close immediately to avoid notifications will open the repository again
+ issue_service.ReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifers)
+
mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, issue, issue.Poster, issue.Content)
if err != nil {
return err
}
-
notify_service.NewPullRequest(ctx, pr, mentions)
if len(issue.Labels) > 0 {
notify_service.IssueChangeLabels(ctx, issue.Poster, issue, issue.Labels, nil)
diff --git a/services/pull/review.go b/services/pull/review.go
index 7d698a14f6..6ad931b679 100644
--- a/services/pull/review.go
+++ b/services/pull/review.go
@@ -20,11 +20,29 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
)
var notEnoughLines = regexp.MustCompile(`fatal: file .* has only \d+ lines?`)
+// ErrDismissRequestOnClosedPR represents an error when an user tries to dismiss a review associated to a closed or merged PR.
+type ErrDismissRequestOnClosedPR struct{}
+
+// IsErrDismissRequestOnClosedPR checks if an error is an ErrDismissRequestOnClosedPR.
+func IsErrDismissRequestOnClosedPR(err error) bool {
+ _, ok := err.(ErrDismissRequestOnClosedPR)
+ return ok
+}
+
+func (err ErrDismissRequestOnClosedPR) Error() string {
+ return "can't dismiss a review associated to a closed or merged PR"
+}
+
+func (err ErrDismissRequestOnClosedPR) Unwrap() error {
+ return util.ErrPermissionDenied
+}
+
// checkInvalidation checks if the line of code comment got changed by another commit.
// If the line got changed the comment is going to be invalidated.
func checkInvalidation(ctx context.Context, c *issues_model.Comment, doer *user_model.User, repo *git.Repository, branch string) error {
@@ -382,6 +400,21 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string,
return nil, fmt.Errorf("reviews's repository is not the same as the one we expect")
}
+ issue := review.Issue
+
+ if issue.IsClosed {
+ return nil, ErrDismissRequestOnClosedPR{}
+ }
+
+ if issue.IsPull {
+ if err := issue.LoadPullRequest(ctx); err != nil {
+ return nil, err
+ }
+ if issue.PullRequest.HasMerged {
+ return nil, ErrDismissRequestOnClosedPR{}
+ }
+ }
+
if err := issues_model.DismissReview(ctx, review, isDismiss); err != nil {
return nil, err
}
diff --git a/services/pull/review_test.go b/services/pull/review_test.go
new file mode 100644
index 0000000000..3bce1e523d
--- /dev/null
+++ b/services/pull/review_test.go
@@ -0,0 +1,48 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package pull_test
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/db"
+ issues_model "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ pull_service "code.gitea.io/gitea/services/pull"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDismissReview(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{})
+ assert.NoError(t, pull.LoadIssue(db.DefaultContext))
+ issue := pull.Issue
+ assert.NoError(t, issue.LoadRepo(db.DefaultContext))
+ reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+ review, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
+ Issue: issue,
+ Reviewer: reviewer,
+ Type: issues_model.ReviewTypeReject,
+ })
+
+ assert.NoError(t, err)
+ issue.IsClosed = true
+ pull.HasMerged = false
+ assert.NoError(t, issues_model.UpdateIssueCols(db.DefaultContext, issue, "is_closed"))
+ assert.NoError(t, pull.UpdateCols(db.DefaultContext, "has_merged"))
+ _, err = pull_service.DismissReview(db.DefaultContext, review.ID, issue.RepoID, "", &user_model.User{}, false, false)
+ assert.Error(t, err)
+ assert.True(t, pull_service.IsErrDismissRequestOnClosedPR(err))
+
+ pull.HasMerged = true
+ pull.Issue.IsClosed = false
+ assert.NoError(t, issues_model.UpdateIssueCols(db.DefaultContext, issue, "is_closed"))
+ assert.NoError(t, pull.UpdateCols(db.DefaultContext, "has_merged"))
+ _, err = pull_service.DismissReview(db.DefaultContext, review.ID, issue.RepoID, "", &user_model.User{}, false, false)
+ assert.Error(t, err)
+ assert.True(t, pull_service.IsErrDismissRequestOnClosedPR(err))
+}
diff --git a/services/user/user.go b/services/user/user.go
index f2648db409..9dc4f6fe62 100644
--- a/services/user/user.go
+++ b/services/user/user.go
@@ -304,5 +304,5 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error {
}
}
- return user_model.DeleteInactiveEmailAddresses(ctx)
+ return nil
}
diff --git a/services/user/user_test.go b/services/user/user_test.go
index 2ebcded925..9013208ed0 100644
--- a/services/user/user_test.go
+++ b/services/user/user_test.go
@@ -7,6 +7,7 @@ import (
"fmt"
"strings"
"testing"
+ "time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/auth"
@@ -16,6 +17,7 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert"
)
@@ -184,3 +186,33 @@ func TestCreateUser_Issue5882(t *testing.T) {
assert.NoError(t, DeleteUser(db.DefaultContext, v.user, false))
}
}
+
+func TestDeleteInactiveUsers(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ // Add an inactive user older than a minute, with an associated email_address record.
+ oldUser := &user_model.User{Name: "OldInactive", LowerName: "oldinactive", Email: "old@example.com", CreatedUnix: timeutil.TimeStampNow().Add(-120)}
+ _, err := db.GetEngine(db.DefaultContext).NoAutoTime().Insert(oldUser)
+ assert.NoError(t, err)
+ oldEmail := &user_model.EmailAddress{UID: oldUser.ID, IsPrimary: true, Email: "old@example.com", LowerEmail: "old@example.com"}
+ err = db.Insert(db.DefaultContext, oldEmail)
+ assert.NoError(t, err)
+
+ // Add an inactive user that's not older than a minute, with an associated email_address record.
+ newUser := &user_model.User{Name: "NewInactive", LowerName: "newinactive", Email: "new@example.com"}
+ err = db.Insert(db.DefaultContext, newUser)
+ assert.NoError(t, err)
+ newEmail := &user_model.EmailAddress{UID: newUser.ID, IsPrimary: true, Email: "new@example.com", LowerEmail: "new@example.com"}
+ err = db.Insert(db.DefaultContext, newEmail)
+ assert.NoError(t, err)
+
+ err = DeleteInactiveUsers(db.DefaultContext, time.Minute)
+ assert.NoError(t, err)
+
+ // User older than a minute should be deleted along with their email address.
+ unittest.AssertExistsIf(t, false, oldUser)
+ unittest.AssertExistsIf(t, false, oldEmail)
+
+ // User not older than a minute shouldn't be deleted and their emaill address should still exist.
+ unittest.AssertExistsIf(t, true, newUser)
+ unittest.AssertExistsIf(t, true, newEmail)
+}
diff --git a/services/webhook/default.go b/services/webhook/default.go
index f725f8a783..be3b9b3c73 100644
--- a/services/webhook/default.go
+++ b/services/webhook/default.go
@@ -10,6 +10,7 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
+ "html/template"
"io"
"net/http"
"net/url"
@@ -17,6 +18,7 @@ import (
webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/svg"
webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/services/forms"
)
@@ -34,6 +36,14 @@ func (dh defaultHandler) Type() webhook_module.HookType {
return webhook_module.GITEA
}
+func (dh defaultHandler) Icon(size int) template.HTML {
+ if dh.forgejo {
+ // forgejo.svg is not in web_src/svg/, so svg.RenderHTML does not work
+ return imgIcon("forgejo.svg", size)
+ }
+ return svg.RenderHTML("gitea-gitea", size, "img")
+}
+
func (defaultHandler) Metadata(*webhook_model.Webhook) any { return nil }
func (defaultHandler) FormFields(bind func(any)) FormFields {
diff --git a/services/webhook/deliver_test.go b/services/webhook/deliver_test.go
index bc06e43e03..c689332666 100644
--- a/services/webhook/deliver_test.go
+++ b/services/webhook/deliver_test.go
@@ -113,7 +113,6 @@ func TestWebhookDeliverAuthorizationHeader(t *testing.T) {
err := hook.SetHeaderAuthorization("Bearer s3cr3t-t0ken")
assert.NoError(t, err)
assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook))
- db.GetEngine(db.DefaultContext).NoAutoTime().DB().Logger.ShowSQL(true)
hookTask := &webhook_model.HookTask{
HookID: hook.ID,
diff --git a/services/webhook/dingtalk.go b/services/webhook/dingtalk.go
index 562e3e86eb..0a0160ac46 100644
--- a/services/webhook/dingtalk.go
+++ b/services/webhook/dingtalk.go
@@ -6,6 +6,7 @@ package webhook
import (
"context"
"fmt"
+ "html/template"
"net/http"
"net/url"
"strings"
@@ -16,14 +17,14 @@ import (
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/services/forms"
-
- dingtalk "gitea.com/lunny/dingtalk_webhook"
)
type dingtalkHandler struct{}
func (dingtalkHandler) Type() webhook_module.HookType { return webhook_module.DINGTALK }
func (dingtalkHandler) Metadata(*webhook_model.Webhook) any { return nil }
+func (dingtalkHandler) Icon(size int) template.HTML { return imgIcon("dingtalk.ico", size) }
+
func (dingtalkHandler) FormFields(bind func(any)) FormFields {
var form struct {
forms.WebhookForm
@@ -42,8 +43,22 @@ func (dingtalkHandler) FormFields(bind func(any)) FormFields {
}
type (
- // DingtalkPayload represents
- DingtalkPayload dingtalk.Payload
+ // DingtalkPayload represents an dingtalk payload.
+ DingtalkPayload struct {
+ MsgType string `json:"msgtype"`
+ Text struct {
+ Content string `json:"content"`
+ } `json:"text"`
+ ActionCard DingtalkActionCard `json:"actionCard"`
+ }
+
+ DingtalkActionCard struct {
+ Text string `json:"text"`
+ Title string `json:"title"`
+ HideAvatar string `json:"hideAvatar"`
+ SingleTitle string `json:"singleTitle"`
+ SingleURL string `json:"singleURL"`
+ }
)
// Create implements PayloadConvertor Create method
@@ -195,7 +210,7 @@ func (dc dingtalkConvertor) Package(p *api.PackagePayload) (DingtalkPayload, err
func createDingtalkPayload(title, text, singleTitle, singleURL string) DingtalkPayload {
return DingtalkPayload{
MsgType: "actionCard",
- ActionCard: dingtalk.ActionCard{
+ ActionCard: DingtalkActionCard{
Text: strings.TrimSpace(text),
Title: strings.TrimSpace(title),
HideAvatar: "0",
diff --git a/services/webhook/discord.go b/services/webhook/discord.go
index 3a0a973860..2efb46f5bb 100644
--- a/services/webhook/discord.go
+++ b/services/webhook/discord.go
@@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
+ "html/template"
"net/http"
"net/url"
"strconv"
@@ -26,6 +27,7 @@ import (
type discordHandler struct{}
func (discordHandler) Type() webhook_module.HookType { return webhook_module.DISCORD }
+func (discordHandler) Icon(size int) template.HTML { return imgIcon("discord.png", size) }
func (discordHandler) FormFields(bind func(any)) FormFields {
var form struct {
diff --git a/services/webhook/feishu.go b/services/webhook/feishu.go
index b27acc9070..eba54fa09b 100644
--- a/services/webhook/feishu.go
+++ b/services/webhook/feishu.go
@@ -6,6 +6,7 @@ package webhook
import (
"context"
"fmt"
+ "html/template"
"net/http"
"strings"
@@ -19,6 +20,7 @@ import (
type feishuHandler struct{}
func (feishuHandler) Type() webhook_module.HookType { return webhook_module.FEISHU }
+func (feishuHandler) Icon(size int) template.HTML { return imgIcon("feishu.png", size) }
func (feishuHandler) FormFields(bind func(any)) FormFields {
var form struct {
diff --git a/services/webhook/general.go b/services/webhook/general.go
index c41f58fe8d..454efc6495 100644
--- a/services/webhook/general.go
+++ b/services/webhook/general.go
@@ -6,7 +6,9 @@ package webhook
import (
"fmt"
"html"
+ "html/template"
"net/url"
+ "strconv"
"strings"
webhook_model "code.gitea.io/gitea/models/webhook"
@@ -352,3 +354,9 @@ func ToHook(repoLink string, w *webhook_model.Webhook) (*api.Hook, error) {
Created: w.CreatedUnix.AsTime(),
}, nil
}
+
+func imgIcon(name string, size int) template.HTML {
+ s := strconv.Itoa(size)
+ src := html.EscapeString(setting.StaticURLPrefix + "/assets/img/" + name)
+ return template.HTML(`<img width="` + s + `" height="` + s + `" src="` + src + `">`)
+}
diff --git a/services/webhook/gogs.go b/services/webhook/gogs.go
index e23673ed35..f616f5e2f3 100644
--- a/services/webhook/gogs.go
+++ b/services/webhook/gogs.go
@@ -4,6 +4,7 @@
package webhook
import (
+ "html/template"
"net/http"
webhook_model "code.gitea.io/gitea/models/webhook"
@@ -14,6 +15,7 @@ import (
type gogsHandler struct{ defaultHandler }
func (gogsHandler) Type() webhook_module.HookType { return webhook_module.GOGS }
+func (gogsHandler) Icon(size int) template.HTML { return imgIcon("gogs.ico", size) }
func (gogsHandler) FormFields(bind func(any)) FormFields {
var form struct {
diff --git a/services/webhook/matrix.go b/services/webhook/matrix.go
index d04f0f367f..322b4d6665 100644
--- a/services/webhook/matrix.go
+++ b/services/webhook/matrix.go
@@ -9,6 +9,7 @@ import (
"crypto/sha1"
"encoding/hex"
"fmt"
+ "html/template"
"net/http"
"net/url"
"regexp"
@@ -20,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/svg"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/services/forms"
@@ -29,6 +31,10 @@ type matrixHandler struct{}
func (matrixHandler) Type() webhook_module.HookType { return webhook_module.MATRIX }
+func (matrixHandler) Icon(size int) template.HTML {
+ return svg.RenderHTML("gitea-matrix", size, "img")
+}
+
func (matrixHandler) FormFields(bind func(any)) FormFields {
var form struct {
forms.WebhookForm
diff --git a/services/webhook/msteams.go b/services/webhook/msteams.go
index 849093e9bf..940a6c49aa 100644
--- a/services/webhook/msteams.go
+++ b/services/webhook/msteams.go
@@ -6,6 +6,7 @@ package webhook
import (
"context"
"fmt"
+ "html/template"
"net/http"
"net/url"
"strings"
@@ -22,6 +23,7 @@ type msteamsHandler struct{}
func (msteamsHandler) Type() webhook_module.HookType { return webhook_module.MSTEAMS }
func (msteamsHandler) Metadata(*webhook_model.Webhook) any { return nil }
+func (msteamsHandler) Icon(size int) template.HTML { return imgIcon("msteams.png", size) }
func (msteamsHandler) FormFields(bind func(any)) FormFields {
var form struct {
diff --git a/services/webhook/packagist.go b/services/webhook/packagist.go
index d8bf9ea23a..f1f3306109 100644
--- a/services/webhook/packagist.go
+++ b/services/webhook/packagist.go
@@ -6,6 +6,7 @@ package webhook
import (
"context"
"fmt"
+ "html/template"
"net/http"
"net/url"
@@ -19,6 +20,7 @@ import (
type packagistHandler struct{}
func (packagistHandler) Type() webhook_module.HookType { return webhook_module.PACKAGIST }
+func (packagistHandler) Icon(size int) template.HTML { return imgIcon("packagist.png", size) }
func (packagistHandler) FormFields(bind func(any)) FormFields {
var form struct {
diff --git a/services/webhook/slack.go b/services/webhook/slack.go
index 683ef41019..0b4c4b6645 100644
--- a/services/webhook/slack.go
+++ b/services/webhook/slack.go
@@ -6,6 +6,7 @@ package webhook
import (
"context"
"fmt"
+ "html/template"
"net/http"
"regexp"
"strings"
@@ -26,6 +27,7 @@ import (
type slackHandler struct{}
func (slackHandler) Type() webhook_module.HookType { return webhook_module.SLACK }
+func (slackHandler) Icon(size int) template.HTML { return imgIcon("slack.png", size) }
type slackForm struct {
forms.WebhookForm
diff --git a/services/webhook/telegram.go b/services/webhook/telegram.go
index 2ede28dbd2..daa986bafb 100644
--- a/services/webhook/telegram.go
+++ b/services/webhook/telegram.go
@@ -6,6 +6,7 @@ package webhook
import (
"context"
"fmt"
+ "html/template"
"net/http"
"net/url"
"strings"
@@ -22,6 +23,7 @@ import (
type telegramHandler struct{}
func (telegramHandler) Type() webhook_module.HookType { return webhook_module.TELEGRAM }
+func (telegramHandler) Icon(size int) template.HTML { return imgIcon("telegram.png", size) }
func (telegramHandler) FormFields(bind func(any)) FormFields {
var form struct {
diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go
index a7802d27dc..f27bffc29a 100644
--- a/services/webhook/webhook.go
+++ b/services/webhook/webhook.go
@@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
+ "html/template"
"net/http"
"strings"
@@ -35,6 +36,7 @@ type Handler interface {
// If form implements the [binding.Validator] interface, the Validate method will be called
FormFields(bind func(form any)) FormFields
NewRequest(context.Context, *webhook_model.Webhook, *webhook_model.HookTask) (req *http.Request, body []byte, err error)
+ Icon(size int) template.HTML
}
type FormFields struct {
diff --git a/services/webhook/wechatwork.go b/services/webhook/wechatwork.go
index 2ad2acd018..eff5b9b526 100644
--- a/services/webhook/wechatwork.go
+++ b/services/webhook/wechatwork.go
@@ -6,6 +6,7 @@ package webhook
import (
"context"
"fmt"
+ "html/template"
"net/http"
"strings"
@@ -21,6 +22,10 @@ type wechatworkHandler struct{}
func (wechatworkHandler) Type() webhook_module.HookType { return webhook_module.WECHATWORK }
func (wechatworkHandler) Metadata(*webhook_model.Webhook) any { return nil }
+func (wechatworkHandler) Icon(size int) template.HTML {
+ return imgIcon("wechatwork.png", size)
+}
+
func (wechatworkHandler) FormFields(bind func(any)) FormFields {
var form struct {
forms.WebhookForm
diff --git a/tailwind.config.js b/tailwind.config.js
index 0754ab3631..d49e9d7a1c 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -41,6 +41,8 @@ export default {
// classes that don't work without CSS variables from "@tailwind base" which we don't use
'transform', 'shadow', 'ring', 'blur', 'grayscale', 'invert', '!invert', 'filter', '!filter',
'backdrop-filter',
+ // we use double-class tw-hidden defined in web_src/css/helpers.css for increased specificity
+ 'hidden',
// unneeded classes
'[-a-zA-Z:0-9_.]',
],
@@ -66,6 +68,10 @@ export default {
'3xl': '24px',
'full': 'var(--border-radius-circle)', // 50%
},
+ fontFamily: {
+ sans: 'var(--fonts-regular)',
+ mono: 'var(--fonts-monospace)',
+ },
fontWeight: {
light: 'var(--font-weight-light)',
normal: 'var(--font-weight-normal)',
diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl
index 2187761828..687a277a84 100644
--- a/templates/admin/auth/edit.tmpl
+++ b/templates/admin/auth/edit.tmpl
@@ -42,7 +42,7 @@
<label for="port">{{ctx.Locale.Tr "admin.auths.port"}}</label>
<input id="port" name="port" value="{{$cfg.Port}}" placeholder="636" required>
</div>
- <div class="has-tls inline field {{if not .HasTLS}}gt-hidden{{end}}">
+ <div class="has-tls inline field {{if not .HasTLS}}tw-hidden{{end}}">
<div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.auths.skip_tls_verify"}}</strong></label>
<input name="skip_verify" type="checkbox" {{if .Source.SkipVerify}}checked{{end}}>
@@ -113,7 +113,7 @@
<input type="checkbox" name="groups_enabled" class="js-ldap-group-toggle" {{if $cfg.GroupsEnabled}}checked{{end}}>
</div>
</div>
- <div id="ldap-group-options" class="ui segment secondary {{if not $cfg.GroupsEnabled}}gt-hidden{{end}}">
+ <div id="ldap-group-options" class="ui segment secondary {{if not $cfg.GroupsEnabled}}tw-hidden{{end}}">
<div class="field">
<label>{{ctx.Locale.Tr "admin.auths.group_search_base"}}</label>
<input name="group_dn" value="{{$cfg.GroupDN}}" placeholder="ou=group,dc=mydomain,dc=com">
@@ -148,7 +148,7 @@
<input id="use_paged_search" name="use_paged_search" type="checkbox" {{if $cfg.UsePagedSearch}}checked{{end}}>
</div>
</div>
- <div class="field required search-page-size{{if not $cfg.UsePagedSearch}} gt-hidden{{end}}">
+ <div class="field required search-page-size{{if not $cfg.UsePagedSearch}} tw-hidden{{end}}">
<label for="search_page_size">{{ctx.Locale.Tr "admin.auths.search_page_size"}}</label>
<input id="search_page_size" name="search_page_size" value="{{if $cfg.UsePagedSearch}}{{$cfg.SearchPageSize}}{{end}}">
</div>
@@ -205,7 +205,7 @@
</div>
<p class="help">{{ctx.Locale.Tr "admin.auths.force_smtps_helper"}}</p>
</div>
- <div class="has-tls inline field {{if not .HasTLS}}gt-hidden{{end}}">
+ <div class="has-tls inline field {{if not .HasTLS}}tw-hidden{{end}}">
<div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.auths.skip_tls_verify"}}</strong></label>
<input name="skip_verify" type="checkbox" {{if $cfg.SkipVerify}}checked{{end}}>
diff --git a/templates/admin/auth/new.tmpl b/templates/admin/auth/new.tmpl
index 255eac202c..f6a14e1f7d 100644
--- a/templates/admin/auth/new.tmpl
+++ b/templates/admin/auth/new.tmpl
@@ -33,13 +33,13 @@
{{template "admin/auth/source/smtp" .}}
<!-- PAM -->
- <div class="pam required field {{if not (eq .type 4)}}gt-hidden{{end}}">
+ <div class="pam required field {{if not (eq .type 4)}}tw-hidden{{end}}">
<label for="pam_service_name">{{ctx.Locale.Tr "admin.auths.pam_service_name"}}</label>
<input id="pam_service_name" name="pam_service_name" value="{{.pam_service_name}}">
<label for="pam_email_domain">{{ctx.Locale.Tr "admin.auths.pam_email_domain"}}</label>
<input id="pam_email_domain" name="pam_email_domain" value="{{.pam_email_domain}}">
</div>
- <div class="pam optional field {{if not (eq .type 4)}}gt-hidden{{end}}">
+ <div class="pam optional field {{if not (eq .type 4)}}tw-hidden{{end}}">
<div class="ui checkbox">
<label for="skip_local_two_fa"><strong>{{ctx.Locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if .skip_local_two_fa}}checked{{end}}>
@@ -59,7 +59,7 @@
<input name="attributes_in_bind" type="checkbox" {{if .attributes_in_bind}}checked{{end}}>
</div>
</div>
- <div class="ldap inline field {{if not (eq .type 2)}}gt-hidden{{end}}">
+ <div class="ldap inline field {{if not (eq .type 2)}}tw-hidden{{end}}">
<div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.auths.syncenabled"}}</strong></label>
<input name="is_sync_enabled" type="checkbox" {{if .is_sync_enabled}}checked{{end}}>
diff --git a/templates/admin/auth/source/ldap.tmpl b/templates/admin/auth/source/ldap.tmpl
index b945a1ab1c..680849c8ea 100644
--- a/templates/admin/auth/source/ldap.tmpl
+++ b/templates/admin/auth/source/ldap.tmpl
@@ -1,4 +1,4 @@
-<div class="ldap dldap field {{if not (or (eq .type 2) (eq .type 5))}}gt-hidden{{end}}">
+<div class="ldap dldap field {{if not (or (eq .type 2) (eq .type 5))}}tw-hidden{{end}}">
<div class="inline required field {{if .Err_SecurityProtocol}}error{{end}}">
<label>{{ctx.Locale.Tr "admin.auths.security_protocol"}}</label>
<div class="ui selection security-protocol dropdown">
@@ -20,17 +20,17 @@
<label for="port">{{ctx.Locale.Tr "admin.auths.port"}}</label>
<input id="port" name="port" value="{{.port}}" placeholder="636">
</div>
- <div class="has-tls inline field {{if not .HasTLS}}gt-hidden{{end}}">
+ <div class="has-tls inline field {{if not .HasTLS}}tw-hidden{{end}}">
<div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.auths.skip_tls_verify"}}</strong></label>
<input name="skip_verify" type="checkbox" {{if .skip_verify}}checked{{end}}>
</div>
</div>
- <div class="ldap field {{if not (eq .type 2)}}gt-hidden{{end}}">
+ <div class="ldap field {{if not (eq .type 2)}}tw-hidden{{end}}">
<label for="bind_dn">{{ctx.Locale.Tr "admin.auths.bind_dn"}}</label>
<input id="bind_dn" name="bind_dn" value="{{.bind_dn}}" placeholder="cn=Search,dc=mydomain,dc=com">
</div>
- <div class="ldap field {{if not (eq .type 2)}}gt-hidden{{end}}">
+ <div class="ldap field {{if not (eq .type 2)}}tw-hidden{{end}}">
<label for="bind_password">{{ctx.Locale.Tr "admin.auths.bind_password"}}</label>
<input id="bind_password" name="bind_password" type="password" autocomplete="off" value="{{.bind_password}}">
</div>
@@ -38,7 +38,7 @@
<label for="user_base">{{ctx.Locale.Tr "admin.auths.user_base"}}</label>
<input id="user_base" name="user_base" value="{{.user_base}}" placeholder="ou=Users,dc=mydomain,dc=com">
</div>
- <div class="dldap required field {{if not (eq .type 5)}}gt-hidden{{end}}">
+ <div class="dldap required field {{if not (eq .type 5)}}tw-hidden{{end}}">
<label for="user_dn">{{ctx.Locale.Tr "admin.auths.user_dn"}}</label>
<input id="user_dn" name="user_dn" value="{{.user_dn}}" placeholder="uid=%s,ou=Users,dc=mydomain,dc=com">
</div>
@@ -115,13 +115,13 @@
</div>
<!-- ldap group end -->
- <div class="ldap inline field {{if not (eq .type 2)}}gt-hidden{{end}}">
+ <div class="ldap inline field {{if not (eq .type 2)}}tw-hidden{{end}}">
<div class="ui checkbox">
<label for="use_paged_search"><strong>{{ctx.Locale.Tr "admin.auths.use_paged_search"}}</strong></label>
<input id="use_paged_search" name="use_paged_search" class="use-paged-search" type="checkbox" {{if .use_paged_search}}checked{{end}}>
</div>
</div>
- <div class="ldap field search-page-size required {{if or (not (eq .type 2)) (not .use_paged_search)}}gt-hidden{{end}}">
+ <div class="ldap field search-page-size required {{if or (not (eq .type 2)) (not .use_paged_search)}}tw-hidden{{end}}">
<label for="search_page_size">{{ctx.Locale.Tr "admin.auths.search_page_size"}}</label>
<input id="search_page_size" name="search_page_size" value="{{.search_page_size}}">
</div>
diff --git a/templates/admin/auth/source/oauth.tmpl b/templates/admin/auth/source/oauth.tmpl
index da3a4f913a..0560cc8256 100644
--- a/templates/admin/auth/source/oauth.tmpl
+++ b/templates/admin/auth/source/oauth.tmpl
@@ -1,4 +1,4 @@
-<div class="oauth2 field {{if not (eq .type 6)}}gt-hidden{{end}}">
+<div class="oauth2 field {{if not (eq .type 6)}}tw-hidden{{end}}">
<div class="inline required field">
<label>{{ctx.Locale.Tr "admin.auths.oauth2_provider"}}</label>
<div class="ui selection type dropdown">
diff --git a/templates/admin/auth/source/smtp.tmpl b/templates/admin/auth/source/smtp.tmpl
index c4b0b0e7e4..31195acf65 100644
--- a/templates/admin/auth/source/smtp.tmpl
+++ b/templates/admin/auth/source/smtp.tmpl
@@ -1,4 +1,4 @@
-<div class="smtp field {{if not (eq .type 3)}}gt-hidden{{end}}">
+<div class="smtp field {{if not (eq .type 3)}}tw-hidden{{end}}">
<div class="inline required field">
<label>{{ctx.Locale.Tr "admin.auths.smtp_auth"}}</label>
<div class="ui selection type dropdown">
diff --git a/templates/admin/auth/source/sspi.tmpl b/templates/admin/auth/source/sspi.tmpl
index f835e89bdf..6a3f00f9a8 100644
--- a/templates/admin/auth/source/sspi.tmpl
+++ b/templates/admin/auth/source/sspi.tmpl
@@ -1,4 +1,4 @@
-<div class="sspi field {{if not (eq .type 7)}}gt-hidden{{end}}">
+<div class="sspi field {{if not (eq .type 7)}}tw-hidden{{end}}">
<div class="field">
<div class="ui checkbox">
<label for="sspi_auto_create_users"><strong>{{ctx.Locale.Tr "admin.auths.sspi_auto_create_users"}}</strong></label>
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl
index 0c944fcb8f..1e94935a16 100644
--- a/templates/admin/config.tmpl
+++ b/templates/admin/config.tmpl
@@ -231,7 +231,7 @@
<dt>{{ctx.Locale.Tr "admin.config.mailer_user"}}</dt>
<dd>{{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}</dd>
<div class="divider"></div>
- <dt class="gt-py-2">{{ctx.Locale.Tr "admin.config.send_test_mail"}}</dt>
+ <dt class="tw-py-1">{{ctx.Locale.Tr "admin.config.send_test_mail"}}</dt>
<dd>
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/admin/config/test_mail" method="post">
{{.CsrfTokenHtml}}
@@ -334,7 +334,7 @@
{{range $loggerName, $loggerDetail := .Loggers}}
<dt>{{ctx.Locale.Tr "admin.config.logger_name_fmt" $loggerName}}</dt>
{{if $loggerDetail.IsEnabled}}
- <dd><pre class="gt-m-0">{{$loggerDetail.EventWriters | JsonUtils.EncodeToString | JsonUtils.PrettyIndent}}</pre></dd>
+ <dd><pre class="tw-m-0">{{$loggerDetail.EventWriters | JsonUtils.EncodeToString | JsonUtils.PrettyIndent}}</pre></dd>
{{else}}
<dd>{{ctx.Locale.Tr "admin.config.disabled_logger"}}</dd>
{{end}}
diff --git a/templates/admin/config_settings.tmpl b/templates/admin/config_settings.tmpl
index 22ad5c24ac..d7fb022274 100644
--- a/templates/admin/config_settings.tmpl
+++ b/templates/admin/config_settings.tmpl
@@ -28,7 +28,7 @@
<div class="field">
<details>
<summary>{{ctx.Locale.Tr "admin.config.open_with_editor_app_help"}}</summary>
- <pre class="gt-px-4">{{.DefaultOpenWithEditorAppsString}}</pre>
+ <pre class="tw-px-4">{{.DefaultOpenWithEditorAppsString}}</pre>
</details>
</div>
<div class="field">
diff --git a/templates/admin/cron.tmpl b/templates/admin/cron.tmpl
index af30cc06e1..3cb641488c 100644
--- a/templates/admin/cron.tmpl
+++ b/templates/admin/cron.tmpl
@@ -5,7 +5,7 @@
</h4>
<div class="ui attached table segment">
<form method="post" action="{{AppSubUrl}}/admin">
- <table class="ui very basic striped table unstackable gt-mb-0">
+ <table class="ui very basic striped table unstackable tw-mb-0">
<thead>
<tr>
<th></th>
diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl
index cc7d338589..bfd2ee6670 100644
--- a/templates/admin/dashboard.tmpl
+++ b/templates/admin/dashboard.tmpl
@@ -11,7 +11,7 @@
<div class="ui attached table segment">
<form method="post" action="{{AppSubUrl}}/admin">
{{.CsrfTokenHtml}}
- <table class="ui very basic table gt-mt-0 gt-px-4">
+ <table class="ui very basic table tw-mt-0 tw-px-4">
<tbody>
<tr>
<td>{{ctx.Locale.Tr "admin.dashboard.delete_inactive_accounts"}}</td>
diff --git a/templates/admin/emails/list.tmpl b/templates/admin/emails/list.tmpl
index b72aef8f35..388863df9b 100644
--- a/templates/admin/emails/list.tmpl
+++ b/templates/admin/emails/list.tmpl
@@ -4,12 +4,12 @@
{{ctx.Locale.Tr "admin.emails.email_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
</h4>
<div class="ui attached segment">
- <div class="ui secondary filter menu tw-items-center gt-mx-0">
+ <div class="ui secondary filter menu tw-items-center tw-mx-0">
<form class="ui form ignore-dirty tw-flex-1">
{{template "shared/search/combo" dict "Value" .Keyword}}
</form>
<!-- Sort -->
- <div class="ui dropdown type jump item gt-mr-0">
+ <div class="ui dropdown type jump item tw-mr-0">
<span class="text">
{{ctx.Locale.Tr "repo.issues.filter_sort"}}
</span>
diff --git a/templates/admin/layout_head.tmpl b/templates/admin/layout_head.tmpl
index b326c82a6c..c1f5fb3314 100644
--- a/templates/admin/layout_head.tmpl
+++ b/templates/admin/layout_head.tmpl
@@ -1,6 +1,6 @@
{{template "base/head" .ctxData}}
<div role="main" aria-label="{{.ctxData.Title}}" class="page-content {{.pageClass}}">
- <div class="ui container gt-mb-4">
+ <div class="ui container">
{{template "base/alert" .ctxData}}
</div>
<div class="ui container fluid padded flex-container">
diff --git a/templates/admin/org/list.tmpl b/templates/admin/org/list.tmpl
index ca0ee30092..987ceab1e0 100644
--- a/templates/admin/org/list.tmpl
+++ b/templates/admin/org/list.tmpl
@@ -7,12 +7,12 @@
</div>
</h4>
<div class="ui attached segment">
- <div class="ui secondary filter menu tw-items-center gt-mx-0">
+ <div class="ui secondary filter menu tw-items-center tw-mx-0">
<form class="ui form ignore-dirty tw-flex-1">
{{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.org_kind")}}
</form>
<!-- Sort -->
- <div class="ui dropdown type jump item gt-mr-0">
+ <div class="ui dropdown type jump item tw-mr-0">
<span class="text">
{{ctx.Locale.Tr "repo.issues.filter_sort"}}
</span>
diff --git a/templates/admin/queue_manage.tmpl b/templates/admin/queue_manage.tmpl
index dd1682a000..dc0196fc6a 100644
--- a/templates/admin/queue_manage.tmpl
+++ b/templates/admin/queue_manage.tmpl
@@ -30,7 +30,7 @@
-
{{else}}
{{$sum}}
- <form action="{{$.Link}}/remove-all-items" method="post" class="tw-inline-block gt-ml-4">
+ <form action="{{$.Link}}/remove-all-items" method="post" class="tw-inline-block tw-ml-4">
{{$.CsrfTokenHtml}}
<button class="ui tiny basic red button">{{ctx.Locale.Tr "admin.monitor.queue.settings.remove_all_items"}}</button>
</form>
diff --git a/templates/admin/repo/unadopted.tmpl b/templates/admin/repo/unadopted.tmpl
index 9166a844a7..6a8e203694 100644
--- a/templates/admin/repo/unadopted.tmpl
+++ b/templates/admin/repo/unadopted.tmpl
@@ -23,7 +23,7 @@
<div class="item tw-flex tw-items-center">
<span class="tw-flex-1"> {{svg "octicon-file-directory-fill"}} {{$dir}}</span>
<div>
- <button class="ui button primary show-modal gt-p-3" data-modal="#adopt-unadopted-modal-{{$dirI}}">{{svg "octicon-plus"}} {{ctx.Locale.Tr "repo.adopt_preexisting_label"}}</button>
+ <button class="ui button primary show-modal tw-p-2" data-modal="#adopt-unadopted-modal-{{$dirI}}">{{svg "octicon-plus"}} {{ctx.Locale.Tr "repo.adopt_preexisting_label"}}</button>
<div class="ui g-modal-confirm modal" id="adopt-unadopted-modal-{{$dirI}}">
<div class="header">
<span class="label">{{ctx.Locale.Tr "repo.adopt_preexisting"}}</span>
@@ -40,7 +40,7 @@
{{template "base/modal_actions_confirm"}}
</form>
</div>
- <button class="ui button red show-modal gt-p-3" data-modal="#delete-unadopted-modal-{{$dirI}}">{{svg "octicon-x"}} {{ctx.Locale.Tr "repo.delete_preexisting_label"}}</button>
+ <button class="ui button red show-modal tw-p-2" data-modal="#delete-unadopted-modal-{{$dirI}}">{{svg "octicon-x"}} {{ctx.Locale.Tr "repo.delete_preexisting_label"}}</button>
<div class="ui g-modal-confirm modal" id="delete-unadopted-modal-{{$dirI}}">
<div class="header">
<span class="label">{{ctx.Locale.Tr "repo.delete_preexisting"}}</span>
diff --git a/templates/admin/self_check.tmpl b/templates/admin/self_check.tmpl
index fafaf9242d..94c4673a49 100644
--- a/templates/admin/self_check.tmpl
+++ b/templates/admin/self_check.tmpl
@@ -7,9 +7,9 @@
<div class="ui attached segment">
{{if .DatabaseCheckHasProblems}}
{{if .DatabaseType.IsMySQL}}
- <div class="gt-p-3">{{ctx.Locale.Tr "admin.self_check.database_fix_mysql"}}</div>
+ <div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mysql"}}</div>
{{else if .DatabaseType.IsMSSQL}}
- <div class="gt-p-3">{{ctx.Locale.Tr "admin.self_check.database_fix_mssql"}}</div>
+ <div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mssql"}}</div>
{{end}}
{{if .DatabaseCheckCollationMismatch}}
<div class="ui red message">{{ctx.Locale.Tr "admin.self_check.database_collation_mismatch" .DatabaseCheckResult.ExpectedCollation}}</div>
@@ -28,7 +28,7 @@
</div>
{{end}}
{{else}}
- <div class="gt-p-3">{{ctx.Locale.Tr "admin.self_check.no_problem_found"}}</div>
+ <div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.no_problem_found"}}</div>
{{end}}
</div>
</div>
diff --git a/templates/admin/stacktrace-row.tmpl b/templates/admin/stacktrace-row.tmpl
index 3f639ba161..694bf56d96 100644
--- a/templates/admin/stacktrace-row.tmpl
+++ b/templates/admin/stacktrace-row.tmpl
@@ -1,6 +1,6 @@
<div class="item">
<div class="tw-flex tw-items-center">
- <div class="icon gt-ml-3 gt-mr-3">
+ <div class="icon tw-ml-2 tw-mr-2">
{{if eq .Process.Type "request"}}
{{svg "octicon-globe" 16}}
{{else if eq .Process.Type "system"}}
@@ -22,14 +22,14 @@
</div>
</div>
{{if .Process.Stacks}}
- <div class="divided list gt-ml-3">
+ <div class="divided list tw-ml-2">
{{range .Process.Stacks}}
<div class="item">
<details>
<summary>
<div class="flex-text-inline">
- <div class="header gt-ml-3">
- <span class="icon gt-mr-3">{{svg "octicon-code" 16}}</span>{{.Description}}{{if gt .Count 1}} * {{.Count}}{{end}}
+ <div class="header tw-ml-2">
+ <span class="icon tw-mr-2">{{svg "octicon-code" 16}}</span>{{.Description}}{{if gt .Count 1}} * {{.Count}}{{end}}
</div>
<div class="description">
{{range .Labels}}
@@ -41,7 +41,7 @@
<div class="list">
{{range .Entry}}
<div class="item tw-flex tw-items-center">
- <span class="icon gt-mr-4">{{svg "octicon-dot-fill" 16}}</span>
+ <span class="icon tw-mr-4">{{svg "octicon-dot-fill" 16}}</span>
<div class="content tw-flex-1">
<div class="header"><code>{{.Function}}</code></div>
<div class="description"><code>{{.File}}:{{.Line}}</code></div>
diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl
index 159c821099..41b00defb4 100644
--- a/templates/admin/user/edit.tmpl
+++ b/templates/admin/user/edit.tmpl
@@ -53,7 +53,7 @@
</div>
</div>
- <div class="required non-local field {{if .Err_LoginName}}error{{end}} {{if eq .User.LoginSource 0}}gt-hidden{{end}}">
+ <div class="required non-local field {{if .Err_LoginName}}error{{end}} {{if eq .User.LoginSource 0}}tw-hidden{{end}}">
<label for="login_name">{{ctx.Locale.Tr "admin.users.auth_login_name"}}</label>
<input id="login_name" name="login_name" value="{{.User.LoginName}}" autofocus>
</div>
@@ -65,7 +65,7 @@
<label for="email">{{ctx.Locale.Tr "email"}}</label>
<input id="email" name="email" type="email" value="{{.User.Email}}" autofocus required>
</div>
- <div class="local field {{if .Err_Password}}error{{end}} {{if not (or (.User.IsLocal) (.User.IsOAuth2))}}gt-hidden{{end}}">
+ <div class="local field {{if .Err_Password}}error{{end}} {{if not (or (.User.IsLocal) (.User.IsOAuth2))}}tw-hidden{{end}}">
<label for="password">{{ctx.Locale.Tr "password"}}</label>
<input id="password" name="password" type="password" autocomplete="new-password">
<p class="help">{{ctx.Locale.Tr "admin.users.password_helper"}}</p>
@@ -128,13 +128,13 @@
<input name="restricted" type="checkbox" {{if .User.IsRestricted}}checked{{end}}>
</div>
</div>
- <div class="inline field {{if DisableGitHooks}}gt-hidden{{end}}">
+ <div class="inline field {{if DisableGitHooks}}tw-hidden{{end}}">
<div class="ui checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.users.allow_git_hook_tooltip"}}">
<label><strong>{{ctx.Locale.Tr "admin.users.allow_git_hook"}}</strong></label>
<input name="allow_git_hook" type="checkbox" {{if .User.CanEditGitHook}}checked{{end}} {{if DisableGitHooks}}disabled{{end}}>
</div>
</div>
- <div class="inline field {{if or (DisableImportLocal) (.DisableMigrations)}}gt-hidden{{end}}">
+ <div class="inline field {{if or (DisableImportLocal) (.DisableMigrations)}}tw-hidden{{end}}">
<div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.users.allow_import_local"}}</strong></label>
<input name="allow_import_local" type="checkbox" {{if .User.CanImportLocal}}checked{{end}} {{if DisableImportLocal}}disabled{{end}}>
@@ -181,7 +181,7 @@
<label>{{ctx.Locale.Tr "settings.lookup_avatar_by_mail"}}</label>
</div>
</div>
- <div class="field gt-pl-4 {{if .Err_Gravatar}}error{{end}}">
+ <div class="field tw-pl-4 {{if .Err_Gravatar}}error{{end}}">
<label for="gravatar">Avatar {{ctx.Locale.Tr "email"}}</label>
<input id="gravatar" name="gravatar" value="{{.User.AvatarEmail}}">
</div>
@@ -194,7 +194,7 @@
</div>
</div>
- <div class="inline field gt-pl-4">
+ <div class="inline field tw-pl-4">
<label for="avatar">{{ctx.Locale.Tr "settings.choose_new_avatar"}}</label>
<input name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp">
</div>
diff --git a/templates/admin/user/new.tmpl b/templates/admin/user/new.tmpl
index bcb53d8131..b04ebc4b60 100644
--- a/templates/admin/user/new.tmpl
+++ b/templates/admin/user/new.tmpl
@@ -47,7 +47,7 @@
</div>
</div>
- <div class="required non-local field {{if .Err_LoginName}}error{{end}} {{if eq .login_type "0-0"}}gt-hidden{{end}}">
+ <div class="required non-local field {{if .Err_LoginName}}error{{end}} {{if eq .login_type "0-0"}}tw-hidden{{end}}">
<label for="login_name">{{ctx.Locale.Tr "admin.users.auth_login_name"}}</label>
<input id="login_name" name="login_name" value="{{.login_name}}">
</div>
@@ -59,12 +59,12 @@
<label for="email">{{ctx.Locale.Tr "email"}}</label>
<input id="email" name="email" type="email" value="{{.email}}" required>
</div>
- <div class="required local field {{if .Err_Password}}error{{end}} {{if not (eq .login_type "0-0")}}gt-hidden{{end}}">
+ <div class="required local field {{if .Err_Password}}error{{end}} {{if not (eq .login_type "0-0")}}tw-hidden{{end}}">
<label for="password">{{ctx.Locale.Tr "password"}}</label>
<input id="password" name="password" type="password" autocomplete="new-password" value="{{.password}}" {{if eq .login_type "0-0"}}required{{end}}>
</div>
- <div class="inline field local {{if ne .login_type "0-0"}}gt-hidden{{end}}">
+ <div class="inline field local {{if ne .login_type "0-0"}}tw-hidden{{end}}">
<div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "auth.allow_password_change"}}</strong></label>
<input name="must_change_password" type="checkbox" checked>
diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl
index fac2809d17..068271bbe9 100644
--- a/templates/base/head_navbar.tmpl
+++ b/templates/base/head_navbar.tmpl
@@ -13,14 +13,14 @@
<!-- mobile right menu, it must be here because in mobile view, each item is a flex column, the first item is a full row column -->
<div class="ui secondary menu item navbar-mobile-right only-mobile">
{{if .IsSigned}}
- <a id="mobile-notifications-icon" class="item tw-w-auto gt-p-3" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}">
+ <a id="mobile-notifications-icon" class="item tw-w-auto tw-p-2" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}">
<div class="tw-relative">
{{svg "octicon-bell"}}
- <span class="notification_count{{if not $notificationUnreadCount}} gt-hidden{{end}}">{{$notificationUnreadCount}}</span>
+ <span class="notification_count{{if not $notificationUnreadCount}} tw-hidden{{end}}">{{$notificationUnreadCount}}</span>
</div>
</a>
{{end}}
- <button class="item tw-w-auto ui icon mini button gt-p-3 gt-m-0" id="navbar-expand-toggle" aria-label="{{ctx.Locale.Tr "toggle_menu"}}">{{svg "octicon-three-bars"}}</button>
+ <button class="item tw-w-auto ui icon mini button tw-p-2 tw-m-0" id="navbar-expand-toggle" aria-label="{{ctx.Locale.Tr "toggle_menu"}}">{{svg "octicon-three-bars"}}</button>
</div>
<!-- navbar links non-mobile -->
@@ -57,8 +57,8 @@
{{if and .IsSigned .MustChangePassword}}
<div class="ui dropdown jump item" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}">
<span class="text tw-flex tw-items-center">
- {{ctx.AvatarUtils.Avatar .SignedUser 24 "gt-mr-2"}}
- <span class="only-mobile gt-ml-3">{{.SignedUser.Name}}</span>
+ {{ctx.AvatarUtils.Avatar .SignedUser 24 "tw-mr-1"}}
+ <span class="only-mobile tw-ml-2">{{.SignedUser.Name}}</span>
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
</span>
<div class="menu user-menu">
@@ -75,19 +75,19 @@
</div><!-- end dropdown avatar menu -->
{{else if .IsSigned}}
{{if EnableTimetracking}}
- <a class="active-stopwatch-trigger item gt-mx-0{{if not .ActiveStopwatch}} gt-hidden{{end}}" href="{{.ActiveStopwatch.IssueLink}}" title="{{ctx.Locale.Tr "active_stopwatch"}}">
+ <a class="active-stopwatch-trigger item tw-mx-0{{if not .ActiveStopwatch}} tw-hidden{{end}}" href="{{.ActiveStopwatch.IssueLink}}" title="{{ctx.Locale.Tr "active_stopwatch"}}">
<div class="tw-relative">
{{svg "octicon-stopwatch"}}
<span class="header-stopwatch-dot"></span>
</div>
- <span class="only-mobile gt-ml-3">{{ctx.Locale.Tr "active_stopwatch"}}</span>
+ <span class="only-mobile tw-ml-2">{{ctx.Locale.Tr "active_stopwatch"}}</span>
</a>
- <div class="active-stopwatch-popup item tippy-target gt-p-3">
+ <div class="active-stopwatch-popup item tippy-target tw-p-2">
<div class="tw-flex tw-items-center">
<a class="stopwatch-link tw-flex tw-items-center" href="{{.ActiveStopwatch.IssueLink}}">
- {{svg "octicon-issue-opened" 16 "gt-mr-3"}}
+ {{svg "octicon-issue-opened" 16 "tw-mr-2"}}
<span class="stopwatch-issue">{{.ActiveStopwatch.RepoSlug}}#{{.ActiveStopwatch.IssueIndex}}</span>
- <span class="ui primary label stopwatch-time gt-my-0 gt-mx-4" data-seconds="{{.ActiveStopwatch.Seconds}}">
+ <span class="ui primary label stopwatch-time tw-my-0 tw-mx-4" data-seconds="{{.ActiveStopwatch.Seconds}}">
{{if .ActiveStopwatch}}{{Sec2Time .ActiveStopwatch.Seconds}}{{end}}
</span>
</a>
@@ -111,14 +111,14 @@
</div>
{{end}}
- <a class="item not-mobile gt-mx-0" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}">
+ <a class="item not-mobile tw-mx-0" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}">
<div class="tw-relative">
{{svg "octicon-bell"}}
- <span class="notification_count{{if not $notificationUnreadCount}} gt-hidden{{end}}">{{$notificationUnreadCount}}</span>
+ <span class="notification_count{{if not $notificationUnreadCount}} tw-hidden{{end}}">{{$notificationUnreadCount}}</span>
</div>
</a>
- <div class="ui dropdown jump item gt-mx-0 gt-pr-3" data-tooltip-content="{{ctx.Locale.Tr "create_new"}}">
+ <div class="ui dropdown jump item tw-mx-0 tw-pr-2" data-tooltip-content="{{ctx.Locale.Tr "create_new"}}">
<span class="text">
{{svg "octicon-plus"}}
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
@@ -141,10 +141,10 @@
</div><!-- end content create new menu -->
</div><!-- end dropdown menu create new -->
- <div class="ui dropdown jump item gt-mx-0 gt-pr-3" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}">
+ <div class="ui dropdown jump item tw-mx-0 tw-pr-2" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}">
<span class="text tw-flex tw-items-center">
- {{ctx.AvatarUtils.Avatar .SignedUser 24 "gt-mr-2"}}
- <span class="only-mobile gt-ml-3">{{.SignedUser.Name}}</span>
+ {{ctx.AvatarUtils.Avatar .SignedUser 24 "tw-mr-1"}}
+ <span class="only-mobile tw-ml-2">{{.SignedUser.Name}}</span>
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
</span>
<div class="menu user-menu">
diff --git a/templates/base/paginate.tmpl b/templates/base/paginate.tmpl
index 4455c20b3f..2ca72f6a34 100644
--- a/templates/base/paginate.tmpl
+++ b/templates/base/paginate.tmpl
@@ -6,11 +6,11 @@
<div class="center page buttons">
<div class="ui borderless pagination menu">
<a class="{{if .IsFirst}}disabled{{end}} item navigation" {{if not .IsFirst}}href="{{$paginationLink}}{{if $paginationParams}}?{{$paginationParams}}{{end}}"{{end}}>
- {{svg "gitea-double-chevron-left" 16 "gt-mr-2"}}
+ {{svg "gitea-double-chevron-left" 16 "tw-mr-1"}}
<span class="navigation_label">{{ctx.Locale.Tr "admin.first_page"}}</span>
</a>
<a class="{{if not .HasPrevious}}disabled{{end}} item navigation" {{if .HasPrevious}}href="{{$paginationLink}}?page={{.Previous}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
- {{svg "octicon-chevron-left" 16 "gt-mr-2"}}
+ {{svg "octicon-chevron-left" 16 "tw-mr-1"}}
<span class="navigation_label">{{ctx.Locale.Tr "repo.issues.previous"}}</span>
</a>
{{range .Pages}}
@@ -22,11 +22,11 @@
{{end}}
<a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$paginationLink}}?page={{.Next}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
<span class="navigation_label">{{ctx.Locale.Tr "repo.issues.next"}}</span>
- {{svg "octicon-chevron-right" 16 "gt-ml-2"}}
+ {{svg "octicon-chevron-right" 16 "tw-ml-1"}}
</a>
<a class="{{if .IsLast}}disabled{{end}} item navigation" {{if not .IsLast}}href="{{$paginationLink}}?page={{.TotalPages}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
<span class="navigation_label">{{ctx.Locale.Tr "admin.last_page"}}</span>
- {{svg "gitea-double-chevron-right" 16 "gt-ml-2"}}
+ {{svg "gitea-double-chevron-right" 16 "tw-ml-1"}}
</a>
</div>
</div>
diff --git a/templates/devtest/fetch-action.tmpl b/templates/devtest/fetch-action.tmpl
index 70844a8751..7b0bbba554 100644
--- a/templates/devtest/fetch-action.tmpl
+++ b/templates/devtest/fetch-action.tmpl
@@ -26,7 +26,7 @@
<div><button name="btn">submit post</button></div>
</form>
<form method="post" action="/no-such-uri" class="form-fetch-action">
- <div class="gt-py-5">bad action url</div>
+ <div class="tw-py-8">bad action url</div>
<div><button name="btn">submit test</button></div>
</form>
</div>
diff --git a/templates/devtest/flex-list.tmpl b/templates/devtest/flex-list.tmpl
index fdb9eb6b39..015ab1e154 100644
--- a/templates/devtest/flex-list.tmpl
+++ b/templates/devtest/flex-list.tmpl
@@ -25,7 +25,7 @@
</div>
<div class="flex-item-trailing">
<button class="ui tiny red button">
- {{svg "octicon-warning" 14}} CJK文本测试
+ {{svg "octicon-alert" 14}} CJK文本测试
</button>
<button class="ui tiny primary button">
{{svg "octicon-info" 14}} Button
@@ -54,7 +54,7 @@
</div>
<div class="flex-item-trailing">
<button class="ui tiny red button">
- {{svg "octicon-warning" 12}} CJK文本测试 <!-- single CJK text test, it shouldn't be horizontal -->
+ {{svg "octicon-alert" 12}} CJK文本测试 <!-- single CJK text test, it shouldn't be horizontal -->
</button>
</div>
</div>
@@ -73,7 +73,7 @@
</div>
<div class="flex-item-trailing">
<a class="muted" href="{{$.Link}}">
- <span class="flex-text-inline"><i class="color-icon gt-mr-3 tw-bg-blue"></i>Go</span>
+ <span class="flex-text-inline"><i class="color-icon tw-mr-2 tw-bg-blue"></i>Go</span>
</a>
<a class="text grey flex-text-inline" href="{{$.Link}}">{{svg "octicon-star" 16}}45000</a>
<a class="text grey flex-text-inline" href="{{$.Link}}">{{svg "octicon-git-branch" 16}}1234</a>
@@ -104,7 +104,7 @@
</div>
<h1>If parent provides the padding/margin space:</h1>
- <div class="tw-border tw-border-secondary gt-py-4">
+ <div class="tw-border tw-border-secondary tw-py-4">
<div class="flex-list flex-space-fitted">
<div class="flex-item">item 1 (no padding top)</div>
<div class="flex-item">item 2 (no padding bottom)</div>
diff --git a/templates/devtest/gitea-ui.tmpl b/templates/devtest/gitea-ui.tmpl
index 9ef0aa675a..583f585226 100644
--- a/templates/devtest/gitea-ui.tmpl
+++ b/templates/devtest/gitea-ui.tmpl
@@ -2,6 +2,13 @@
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/devtest.css?v={{AssetVersion}}">
<div class="page-content devtest ui container">
<div>
+ <h1>Link</h1>
+ <div>
+ <a href="#">normal</a>
+ <a class="muted" href="#">muted</a>
+ <a class="suppressed" href="#">suppressed</a>
+ <a class="silenced" href="#">silenced</a>
+ </div>
<h1>Button</h1>
<div>
Style:
@@ -60,10 +67,10 @@
</li>
<li class="sample-group">
<h2>Inline / Plain:</h2>
- <div class="gt-my-2">
- <button class="btn gt-p-3">Plain button</button>
- <button class="btn interact-fg gt-p-3">Plain button with interact fg</button>
- <button class="btn interact-bg gt-p-3">Plain button with interact bg</button>
+ <div class="tw-my-1">
+ <button class="btn tw-p-2">Plain button</button>
+ <button class="btn interact-fg tw-p-2">Plain button with interact fg</button>
+ <button class="btn interact-bg tw-p-2">Plain button with interact bg</button>
</div>
</li>
</ul>
@@ -95,8 +102,8 @@
<div>
<h1>Loading</h1>
- <div class="is-loading small-loading-icon tw-border tw-border-secondary gt-py-2"><span>loading ...</span></div>
- <div class="is-loading tw-border tw-border-secondary gt-py-4">
+ <div class="is-loading small-loading-icon tw-border tw-border-secondary tw-py-1"><span>loading ...</span></div>
+ <div class="is-loading tw-border tw-border-secondary tw-py-4">
<p>loading ...</p>
<p>loading ...</p>
<p>loading ...</p>
diff --git a/templates/explore/navbar.tmpl b/templates/explore/navbar.tmpl
index 8841613b9f..8e619fa66f 100644
--- a/templates/explore/navbar.tmpl
+++ b/templates/explore/navbar.tmpl
@@ -1,4 +1,4 @@
-<overflow-menu class="ui secondary pointing tabular top attached borderless menu navbar">
+<overflow-menu class="ui secondary pointing tabular top attached borderless menu secondary-nav">
<div class="overflow-menu-items tw-justify-center">
<a class="{{if .PageIsExploreRepositories}}active {{end}}item" href="{{AppSubUrl}}/explore/repos">
{{svg "octicon-repo"}} {{ctx.Locale.Tr "explore.repos"}}
diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl
index 9e5042d650..a59235abc1 100644
--- a/templates/explore/repo_list.tmpl
+++ b/templates/explore/repo_list.tmpl
@@ -33,20 +33,20 @@
<div class="flex-item-trailing muted-links">
{{if .PrimaryLanguage}}
<a class="flex-text-inline" href="?q={{$.Keyword}}&sort={{$.SortType}}&language={{.PrimaryLanguage.Language}}{{if $.TabName}}&tab={{$.TabName}}{{end}}">
- <i class="color-icon gt-mr-3" style="background-color: {{.PrimaryLanguage.Color}}"></i>
+ <i class="color-icon tw-mr-2" style="background-color: {{.PrimaryLanguage.Color}}"></i>
{{.PrimaryLanguage.Language}}
</a>
{{end}}
{{if not $.DisableStars}}
- <a class="flex-text-inline" href="{{.Link}}/stars">
- <span aria-label="{{ctx.Locale.Tr "repo.stars"}}">{{svg "octicon-star" 16}}</span>
- <span {{if ge .NumStars 1000}}data-tooltip-content="{{.NumStars}}"{{end}}>{{CountFmt .NumStars}}</span>
+ <a class="flex-text-inline" href="{{.Link}}/stars" aria-label="{{ctx.Locale.TrN .NumStars "explore.stars_one" "explore.stars_few" .NumStars}}">
+ {{svg "octicon-star" 16}}
+ {{if ge .NumStars 1000}}data-tooltip-content="{{.NumStars}}"{{end}}{{CountFmt .NumStars}}
</a>
{{end}}
{{if not $.DisableForks}}
- <a class="flex-text-inline" href="{{.Link}}/forks">
- <span aria-label="{{ctx.Locale.Tr "repo.forks"}}">{{svg "octicon-git-branch" 16}}</span>
- <span {{if ge .NumForks 1000}}data-tooltip-content="{{.NumForks}}"{{end}}>{{CountFmt .NumForks}}</span>
+ <a class="flex-text-inline" href="{{.Link}}/forks" aria-label="{{ctx.Locale.TrN .NumForks "explore.forks_one" "explore.forks_few" .NumForks}}">
+ {{svg "octicon-git-branch" 16}}
+ {{if ge .NumForks 1000}}data-tooltip-content="{{.NumForks}}"{{end}}{{CountFmt .NumForks}}
</a>
{{end}}
</div>
diff --git a/templates/explore/search.tmpl b/templates/explore/search.tmpl
index c12ff325f9..1d984a2e37 100644
--- a/templates/explore/search.tmpl
+++ b/templates/explore/search.tmpl
@@ -1,4 +1,4 @@
-<div class="ui small secondary filter menu tw-items-center gt-mx-0">
+<div class="ui small secondary filter menu tw-items-center tw-mx-0">
<form class="ui form ignore-dirty tw-flex-1">
{{if .PageIsExploreUsers}}
{{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.user_kind")}}
@@ -7,7 +7,7 @@
{{end}}
</form>
<!-- Sort -->
- <div class="ui small dropdown type jump item gt-mr-0">
+ <div class="ui small dropdown type jump item tw-mr-0">
<span class="text">
{{ctx.Locale.Tr "repo.issues.filter_sort"}}
</span>
diff --git a/templates/home.tmpl b/templates/home.tmpl
index 1e5369e7ee..e6fd4ef020 100644
--- a/templates/home.tmpl
+++ b/templates/home.tmpl
@@ -1,6 +1,6 @@
{{template "base/head" .}}
<div role="main" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home"}}{{end}}" class="page-content home">
- <div class="gt-mb-5 gt-px-5">
+ <div class="tw-mb-8 tw-px-8">
<div class="center">
<img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}">
<div class="hero">
diff --git a/templates/install.tmpl b/templates/install.tmpl
index b3aea39ee5..6bb37d0a0c 100644
--- a/templates/install.tmpl
+++ b/templates/install.tmpl
@@ -8,7 +8,7 @@
<div class="ui attached segment">
{{template "base/alert" .}}
- <p>{{ctx.Locale.Tr "install.docker_helper" "https://docs.gitea.com/installation/install-with-docker"}}</p>
+ <p>{{ctx.Locale.Tr "install.docker_helper" "https://forgejo.org/docs/latest/admin/installation-docker/"}}</p>
<form class="ui form" action="{{AppSubUrl}}/" method="post">
<!-- Database Settings -->
@@ -28,7 +28,7 @@
</div>
</div>
- <div class="gt-mt-4 gt-hidden" data-db-setting-for="common-host">
+ <div class="tw-mt-4 tw-hidden" data-db-setting-for="common-host">
<div class="inline required field {{if .Err_DbSetting}}error{{end}}">
<label for="db_host">{{ctx.Locale.Tr "install.host"}}</label>
<input id="db_host" name="db_host" value="{{.db_host}}">
@@ -47,7 +47,7 @@
</div>
</div>
- <div class="gt-mt-4 gt-hidden" data-db-setting-for="postgres">
+ <div class="tw-mt-4 tw-hidden" data-db-setting-for="postgres">
<div class="inline required field">
<label>{{ctx.Locale.Tr "install.ssl_mode"}}</label>
<div class="ui selection database type dropdown">
@@ -68,7 +68,7 @@
</div>
</div>
- <div class="gt-mt-4 gt-hidden" data-db-setting-for="sqlite3">
+ <div class="tw-mt-4 tw-hidden" data-db-setting-for="sqlite3">
<div class="inline required field {{if or .Err_DbPath .Err_DbSetting}}error{{end}}">
<label for="db_path">{{ctx.Locale.Tr "install.path"}}</label>
<input id="db_path" name="db_path" value="{{.db_path}}">
@@ -160,7 +160,7 @@
<!-- Email -->
<details class="optional field">
- <summary class="right-content gt-py-3{{if .Err_SMTP}} text red{{end}}">
+ <summary class="right-content tw-py-2{{if .Err_SMTP}} text red{{end}}">
{{ctx.Locale.Tr "install.email_title"}}
</summary>
<div class="inline field">
@@ -200,7 +200,7 @@
<!-- Server and other services -->
<details class="optional field">
- <summary class="right-content gt-py-3{{if .Err_Services}} text red{{end}}">
+ <summary class="right-content tw-py-2{{if .Err_Services}} text red{{end}}">
{{ctx.Locale.Tr "install.server_service_title"}}
</summary>
<div class="inline field">
@@ -298,7 +298,7 @@
<!-- Admin -->
<details class="optional field">
- <summary class="right-content gt-py-3{{if .Err_Admin}} text red{{end}}">
+ <summary class="right-content tw-py-2{{if .Err_Admin}} text red{{end}}">
{{ctx.Locale.Tr "install.admin_title"}}
</summary>
<p class="center">{{ctx.Locale.Tr "install.admin_setting_desc"}}</p>
@@ -327,7 +327,7 @@
<div class="right-content">
{{ctx.Locale.Tr "install.env_config_keys_prompt"}}
</div>
- <div class="right-content gt-mt-3">
+ <div class="right-content tw-mt-2">
{{range .EnvConfigKeys}}<span class="ui label">{{.}}</span>{{end}}
</div>
</div>
@@ -338,7 +338,7 @@
<div class="right-content">
{{ctx.Locale.Tr "install.config_location_hint"}} {{.CustomConfFile}}
</div>
- <div class="right-content gt-mt-3">
+ <div class="right-content tw-mt-2">
<button class="ui primary button">{{ctx.Locale.Tr "install.install_btn_confirm"}}</button>
</div>
</div>
@@ -347,5 +347,5 @@
</div>
</div>
</div>
-<img class="gt-hidden" src="{{AssetUrlPrefix}}/img/forgejo-loading.svg" width="256" height="256">
+<img class="tw-hidden" src="{{AssetUrlPrefix}}/img/forgejo-loading.svg" width="256" height="256">
{{template "base/footer" .}}
diff --git a/templates/org/follow_unfollow.tmpl b/templates/org/follow_unfollow.tmpl
index b9a3bb77fe..ba0bd01efe 100644
--- a/templates/org/follow_unfollow.tmpl
+++ b/templates/org/follow_unfollow.tmpl
@@ -1,4 +1,4 @@
-<button class="ui basic button gt-mr-0" hx-post="{{.Org.HomeLink}}?action={{if $.IsFollowing}}unfollow{{else}}follow{{end}}">
+<button class="ui basic button tw-mr-0" hx-post="{{.Org.HomeLink}}?action={{if $.IsFollowing}}unfollow{{else}}follow{{end}}">
{{if $.IsFollowing}}
{{ctx.Locale.Tr "user.unfollow"}}
{{else}}
diff --git a/templates/org/header.tmpl b/templates/org/header.tmpl
index 6eb7feb33f..7361df99ea 100644
--- a/templates/org/header.tmpl
+++ b/templates/org/header.tmpl
@@ -9,7 +9,7 @@
</span>
<span class="tw-flex tw-items-center tw-gap-1 tw-ml-auto tw-text-16 tw-whitespace-nowrap">
{{if .EnableFeed}}
- <a class="ui basic label button gt-mr-0" href="{{.Org.HomeLink}}.rss" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">
+ <a class="ui basic label button tw-mr-0" href="{{.Org.HomeLink}}.rss" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">
{{svg "octicon-rss" 24}}
</a>
{{end}}
@@ -19,7 +19,7 @@
</span>
</div>
{{if .RenderedDescription}}<div class="render-content markup">{{.RenderedDescription}}</div>{{end}}
- <div class="text light meta gt-mt-2">
+ <div class="text light meta tw-mt-1">
{{if .Org.Location}}<div class="flex-text-block">{{svg "octicon-location"}} <span>{{.Org.Location}}</span></div>{{end}}
{{if .Org.Website}}<div class="flex-text-block">{{svg "octicon-link"}} <a class="muted" target="_blank" rel="noopener noreferrer me" href="{{.Org.Website}}">{{.Org.Website}}</a></div>{{end}}
{{if .IsSigned}}
diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl
index 920a42a5da..45697e566c 100644
--- a/templates/org/home.tmpl
+++ b/templates/org/home.tmpl
@@ -1,7 +1,7 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content organization profile">
{{if .Flash}}
- <div class="ui container gt-mb-5">
+ <div class="ui container tw-mb-8">
{{template "base/alert" .}}
</div>
{{end}}
@@ -21,7 +21,7 @@
{{if .ShowMemberAndTeamTab}}
<div class="ui five wide column">
{{if .CanCreateOrgRepo}}
- <div class="center aligned">
+ <div class="center aligned tw-mb-4">
<a class="ui primary button" href="{{AppSubUrl}}/repo/create?org={{.Org.ID}}">{{ctx.Locale.Tr "new_repo"}}</a>
{{if not .DisableNewPullMirrors}}
<a class="ui primary button" href="{{AppSubUrl}}/repo/migrate?org={{.Org.ID}}&mirror=1">{{ctx.Locale.Tr "new_migrate"}}</a>
diff --git a/templates/org/menu.tmpl b/templates/org/menu.tmpl
index 8eacc17e82..c519606d1f 100644
--- a/templates/org/menu.tmpl
+++ b/templates/org/menu.tmpl
@@ -1,5 +1,5 @@
<div class="ui container">
- <overflow-menu class="ui secondary pointing tabular borderless menu">
+ <overflow-menu class="ui secondary pointing tabular borderless menu tw-mb-4">
<div class="overflow-menu-items">
<a class="{{if .PageIsViewRepositories}}active {{end}}item" href="{{$.Org.HomeLink}}">
{{svg "octicon-repo"}} {{ctx.Locale.Tr "user.repositories"}}
diff --git a/templates/org/projects/list.tmpl b/templates/org/projects/list.tmpl
index 97cc6cf66c..ec9cfece9a 100644
--- a/templates/org/projects/list.tmpl
+++ b/templates/org/projects/list.tmpl
@@ -14,7 +14,7 @@
{{template "shared/user/profile_big_avatar" .}}
</div>
<div class="ui twelve wide column">
- <div class="gt-mb-4">
+ <div class="tw-mb-4">
{{template "user/overview/header" .}}
</div>
{{template "projects/list" .}}
diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl
index 31c0d85d89..62debfc0ae 100644
--- a/templates/org/settings/options.tmpl
+++ b/templates/org/settings/options.tmpl
@@ -8,7 +8,7 @@
{{.CsrfTokenHtml}}
<div class="required field {{if .Err_Name}}error{{end}}">
<label for="org_name">{{ctx.Locale.Tr "org.org_name_holder"}}
- <span class="text red gt-hidden" id="org-name-change-prompt">
+ <span class="text red tw-hidden" id="org-name-change-prompt">
<br>{{ctx.Locale.Tr "org.settings.change_orgname_prompt"}}<br>{{ctx.Locale.Tr "org.settings.change_orgname_redirect_prompt"}}
</span>
</label>
diff --git a/templates/org/team/members.tmpl b/templates/org/team/members.tmpl
index aa358841da..5719328a27 100644
--- a/templates/org/team/members.tmpl
+++ b/templates/org/team/members.tmpl
@@ -12,7 +12,7 @@
<form class="ui form ignore-dirty tw-flex tw-flex-wrap tw-gap-2" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/add" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="uid" value="{{.SignedUser.ID}}">
- <div id="search-user-box" class="ui search gt-mr-3"{{if .IsEmailInviteEnabled}} data-allow-email="true" data-allow-email-description="{{ctx.Locale.Tr "org.teams.invite_team_member" $.Team.Name}}"{{end}}>
+ <div id="search-user-box" class="ui search tw-mr-2"{{if .IsEmailInviteEnabled}} data-allow-email="true" data-allow-email-description="{{ctx.Locale.Tr "org.teams.invite_team_member" $.Team.Name}}"{{end}}>
<div class="ui input">
<input class="prompt" name="uname" placeholder="{{ctx.Locale.Tr "search.user_kind"}}" autocomplete="off" required>
</div>
diff --git a/templates/org/team/new.tmpl b/templates/org/team/new.tmpl
index d1e0dbe382..9608eac154 100644
--- a/templates/org/team/new.tmpl
+++ b/templates/org/team/new.tmpl
@@ -71,18 +71,18 @@
</div>
<div class="divider"></div>
- <div class="team-units required grouped field {{if eq .Team.AccessMode 3}}gt-hidden{{end}}">
+ <div class="team-units required grouped field {{if eq .Team.AccessMode 3}}tw-hidden{{end}}">
<label>{{ctx.Locale.Tr "org.team_unit_desc"}}</label>
<table class="ui celled table">
<thead>
<tr>
<th>{{ctx.Locale.Tr "units.unit"}}</th>
<th class="center aligned">{{ctx.Locale.Tr "org.teams.none_access"}}
- <span class="tw-align-middle" data-tooltip-content="{{ctx.Locale.Tr "org.teams.none_access_helper"}}">{{svg "octicon-question" 16 "gt-ml-2"}}</span></th>
+ <span class="tw-align-middle" data-tooltip-content="{{ctx.Locale.Tr "org.teams.none_access_helper"}}">{{svg "octicon-question" 16 "tw-ml-1"}}</span></th>
<th class="center aligned">{{ctx.Locale.Tr "org.teams.read_access"}}
- <span class="tw-align-middle" data-tooltip-content="{{ctx.Locale.Tr "org.teams.read_access_helper"}}">{{svg "octicon-question" 16 "gt-ml-2"}}</span></th>
+ <span class="tw-align-middle" data-tooltip-content="{{ctx.Locale.Tr "org.teams.read_access_helper"}}">{{svg "octicon-question" 16 "tw-ml-1"}}</span></th>
<th class="center aligned">{{ctx.Locale.Tr "org.teams.write_access"}}
- <span class="tw-align-middle" data-tooltip-content="{{ctx.Locale.Tr "org.teams.write_access_helper"}}">{{svg "octicon-question" 16 "gt-ml-2"}}</span></th>
+ <span class="tw-align-middle" data-tooltip-content="{{ctx.Locale.Tr "org.teams.write_access_helper"}}">{{svg "octicon-question" 16 "tw-ml-1"}}</span></th>
</tr>
</thead>
<tbody>
diff --git a/templates/org/team/repositories.tmpl b/templates/org/team/repositories.tmpl
index 202279240b..98b4854eb8 100644
--- a/templates/org/team/repositories.tmpl
+++ b/templates/org/team/repositories.tmpl
@@ -17,7 +17,7 @@
<input class="prompt" name="repo_name" placeholder="{{ctx.Locale.Tr "search.repo_kind"}}" autocomplete="off" required>
</div>
</div>
- <button class="ui primary button gt-ml-3">{{ctx.Locale.Tr "add"}}</button>
+ <button class="ui primary button tw-ml-2">{{ctx.Locale.Tr "add"}}</button>
</form>
<div class="tw-inline-block">
<button class="ui primary button link-action" data-modal-confirm="{{ctx.Locale.Tr "org.teams.add_all_repos_desc"}}" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/addall">{{ctx.Locale.Tr "add_all"}}</button>
diff --git a/templates/package/metadata/alpine.tmpl b/templates/package/metadata/alpine.tmpl
index 73cbc06aac..3e7f10f66a 100644
--- a/templates/package/metadata/alpine.tmpl
+++ b/templates/package/metadata/alpine.tmpl
@@ -1,5 +1,5 @@
{{if eq .PackageDescriptor.Package.Type "alpine"}}
{{if .PackageDescriptor.Metadata.Maintainer}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "mr-3"}} {{.PackageDescriptor.Metadata.Maintainer}}</div>{{end}}
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
{{end}}
diff --git a/templates/package/metadata/cargo.tmpl b/templates/package/metadata/cargo.tmpl
index c8471a71ef..5ad3c20a93 100644
--- a/templates/package/metadata/cargo.tmpl
+++ b/templates/package/metadata/cargo.tmpl
@@ -1,7 +1,7 @@
{{if eq .PackageDescriptor.Package.Type "cargo"}}
- {{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "gt-mr-3"}} {{.}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
+ {{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
{{end}}
diff --git a/templates/package/metadata/chef.tmpl b/templates/package/metadata/chef.tmpl
index fa6e068d23..23a9ce3ec0 100644
--- a/templates/package/metadata/chef.tmpl
+++ b/templates/package/metadata/chef.tmpl
@@ -1,5 +1,5 @@
{{if eq .PackageDescriptor.Package.Type "chef"}}
- {{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
{{end}}
diff --git a/templates/package/metadata/composer.tmpl b/templates/package/metadata/composer.tmpl
index fbdc33f73d..0f6ff9d6f2 100644
--- a/templates/package/metadata/composer.tmpl
+++ b/templates/package/metadata/composer.tmpl
@@ -1,5 +1,5 @@
{{if eq .PackageDescriptor.Package.Type "composer"}}
- {{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "gt-mr-3"}} {{.Name}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.Homepage}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.Homepage}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
- {{range .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.}}</div>{{end}}
+ {{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.Name}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.Homepage}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.Homepage}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
+ {{range .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}</div>{{end}}
{{end}}
diff --git a/templates/package/metadata/conan.tmpl b/templates/package/metadata/conan.tmpl
index 40bda555bb..4e05ec2587 100644
--- a/templates/package/metadata/conan.tmpl
+++ b/templates/package/metadata/conan.tmpl
@@ -1,6 +1,6 @@
{{if eq .PackageDescriptor.Package.Type "conan"}}
- {{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
{{end}}
diff --git a/templates/package/metadata/conda.tmpl b/templates/package/metadata/conda.tmpl
index f70e2b2a1c..3628686e13 100644
--- a/templates/package/metadata/conda.tmpl
+++ b/templates/package/metadata/conda.tmpl
@@ -1,6 +1,6 @@
{{if eq .PackageDescriptor.Package.Type "conda"}}
- {{if .PackageDescriptor.Metadata.License}}<div class="item">{{svg "octicon-law" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.License}}<div class="item">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
{{end}}
diff --git a/templates/package/metadata/container.tmpl b/templates/package/metadata/container.tmpl
index b05ef0b846..f5abb7ef6e 100644
--- a/templates/package/metadata/container.tmpl
+++ b/templates/package/metadata/container.tmpl
@@ -1,9 +1,9 @@
{{if eq .PackageDescriptor.Package.Type "container"}}
- <div class="item" title="{{ctx.Locale.Tr "packages.container.details.type"}}">{{svg "octicon-package" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.Type.Name}}</div>
- {{if .PackageDescriptor.Metadata.Platform}}<div class="item" title="{{ctx.Locale.Tr "packages.container.details.platform"}}">{{svg "octicon-cpu" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.Platform}}</div>{{end}}
- {{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "gt-mr-3"}} {{.}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.Licenses}}<div class="item">{{svg "octicon-law" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.Licenses}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
+ <div class="item" title="{{ctx.Locale.Tr "packages.container.details.type"}}">{{svg "octicon-package" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Type.Name}}</div>
+ {{if .PackageDescriptor.Metadata.Platform}}<div class="item" title="{{ctx.Locale.Tr "packages.container.details.platform"}}">{{svg "octicon-cpu" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Platform}}</div>{{end}}
+ {{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.Licenses}}<div class="item">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Licenses}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
{{end}}
diff --git a/templates/package/metadata/helm.tmpl b/templates/package/metadata/helm.tmpl
index 499f77e80d..50ea484999 100644
--- a/templates/package/metadata/helm.tmpl
+++ b/templates/package/metadata/helm.tmpl
@@ -1,4 +1,4 @@
{{if eq .PackageDescriptor.Package.Type "helm"}}
- {{range .PackageDescriptor.Metadata.Maintainers}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "gt-mr-3"}} {{.Name}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.Home}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.Home}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
+ {{range .PackageDescriptor.Metadata.Maintainers}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.Name}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.Home}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.Home}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
{{end}}
diff --git a/templates/package/metadata/maven.tmpl b/templates/package/metadata/maven.tmpl
index 36f5eca840..548be61790 100644
--- a/templates/package/metadata/maven.tmpl
+++ b/templates/package/metadata/maven.tmpl
@@ -1,5 +1,5 @@
{{if eq .PackageDescriptor.Package.Type "maven"}}
- {{if .PackageDescriptor.Metadata.Name}}<div class="item">{{svg "octicon-note" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.Name}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
- {{range .PackageDescriptor.Metadata.Licenses}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.Name}}<div class="item">{{svg "octicon-note" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Name}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
+ {{range .PackageDescriptor.Metadata.Licenses}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}</div>{{end}}
{{end}}
diff --git a/templates/package/metadata/npm.tmpl b/templates/package/metadata/npm.tmpl
index 9794d851af..df37504e37 100644
--- a/templates/package/metadata/npm.tmpl
+++ b/templates/package/metadata/npm.tmpl
@@ -1,8 +1,8 @@
{{if eq .PackageDescriptor.Package.Type "npm"}}
- {{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
{{range .PackageDescriptor.VersionProperties}}
- {{if eq .Name "npm.tag"}}<div class="item" title="{{ctx.Locale.Tr "packages.npm.details.tag"}}">{{svg "octicon-versions" 16 "gt-mr-3"}} {{.Value}}</div>{{end}}
+ {{if eq .Name "npm.tag"}}<div class="item" title="{{ctx.Locale.Tr "packages.npm.details.tag"}}">{{svg "octicon-versions" 16 "tw-mr-2"}} {{.Value}}</div>{{end}}
{{end}}
{{end}}
diff --git a/templates/package/metadata/nuget.tmpl b/templates/package/metadata/nuget.tmpl
index f25e1c3b63..5534577bd2 100644
--- a/templates/package/metadata/nuget.tmpl
+++ b/templates/package/metadata/nuget.tmpl
@@ -1,5 +1,5 @@
{{if eq .PackageDescriptor.Package.Type "nuget"}}
- {{if .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.Authors}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Authors}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
{{end}}
diff --git a/templates/package/metadata/pub.tmpl b/templates/package/metadata/pub.tmpl
index 1e4a90e78c..16f7cec370 100644
--- a/templates/package/metadata/pub.tmpl
+++ b/templates/package/metadata/pub.tmpl
@@ -1,5 +1,5 @@
{{if eq .PackageDescriptor.Package.Type "pub"}}
- {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.documentation_site"}}</a></div>{{end}}
{{end}}
diff --git a/templates/package/metadata/pypi.tmpl b/templates/package/metadata/pypi.tmpl
index f447cb7f4f..3d9b213907 100644
--- a/templates/package/metadata/pypi.tmpl
+++ b/templates/package/metadata/pypi.tmpl
@@ -1,5 +1,5 @@
{{if eq .PackageDescriptor.Package.Type "pypi"}}
- {{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
{{end}}
diff --git a/templates/package/metadata/rpm.tmpl b/templates/package/metadata/rpm.tmpl
index 026f129590..eda8a489f3 100644
--- a/templates/package/metadata/rpm.tmpl
+++ b/templates/package/metadata/rpm.tmpl
@@ -1,4 +1,4 @@
{{if eq .PackageDescriptor.Package.Type "rpm"}}
- {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
{{end}}
diff --git a/templates/package/metadata/rubygems.tmpl b/templates/package/metadata/rubygems.tmpl
index 62150b1a43..9b11287691 100644
--- a/templates/package/metadata/rubygems.tmpl
+++ b/templates/package/metadata/rubygems.tmpl
@@ -1,5 +1,5 @@
{{if eq .PackageDescriptor.Package.Type "rubygems"}}
- {{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "gt-mr-3"}} {{.}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div> {{end}}
- {{range .PackageDescriptor.Metadata.Licenses}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.}}</div>{{end}}
+ {{range .PackageDescriptor.Metadata.Authors}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div> {{end}}
+ {{range .PackageDescriptor.Metadata.Licenses}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}</div>{{end}}
{{end}}
diff --git a/templates/package/metadata/swift.tmpl b/templates/package/metadata/swift.tmpl
index 326ebe1a94..fdffb6dede 100644
--- a/templates/package/metadata/swift.tmpl
+++ b/templates/package/metadata/swift.tmpl
@@ -1,4 +1,4 @@
{{if eq .PackageDescriptor.Package.Type "swift"}}
{{if .PackageDescriptor.Metadata.Author.String}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "mr-3"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
{{end}}
diff --git a/templates/package/metadata/vagrant.tmpl b/templates/package/metadata/vagrant.tmpl
index a92398a275..4628a2dcbb 100644
--- a/templates/package/metadata/vagrant.tmpl
+++ b/templates/package/metadata/vagrant.tmpl
@@ -1,5 +1,5 @@
{{if eq .PackageDescriptor.Package.Type "vagrant"}}
- {{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "gt-mr-3"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
- {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
- {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.Author}}<div class="item" title="{{ctx.Locale.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.Author}}</div>{{end}}
+ {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
+ {{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.repository_site"}}</a></div>{{end}}
{{end}}
diff --git a/templates/package/shared/list.tmpl b/templates/package/shared/list.tmpl
index 3e99285a06..36f8bc1522 100644
--- a/templates/package/shared/list.tmpl
+++ b/templates/package/shared/list.tmpl
@@ -50,7 +50,7 @@
<p>{{ctx.Locale.Tr "packages.empty.documentation" "https://forgejo.org/docs/latest/user/packages/"}}</p>
</div>
{{else}}
- <p class="gt-py-4">{{ctx.Locale.Tr "packages.filter.no_result"}}</p>
+ <p class="tw-py-4">{{ctx.Locale.Tr "packages.filter.no_result"}}</p>
{{end}}
{{end}}
{{template "base/paginate" .}}
diff --git a/templates/package/shared/versionlist.tmpl b/templates/package/shared/versionlist.tmpl
index fc34ccc938..e5c568e059 100644
--- a/templates/package/shared/versionlist.tmpl
+++ b/templates/package/shared/versionlist.tmpl
@@ -31,7 +31,7 @@
</div>
</div>
{{else}}
- <p class="gt-py-4">{{ctx.Locale.Tr "packages.filter.no_result"}}</p>
+ <p class="tw-py-4">{{ctx.Locale.Tr "packages.filter.no_result"}}</p>
{{end}}
{{template "base/paginate" .}}
</div>
diff --git a/templates/package/view.tmpl b/templates/package/view.tmpl
index e81a714895..6beb249a7f 100644
--- a/templates/package/view.tmpl
+++ b/templates/package/view.tmpl
@@ -43,12 +43,12 @@
<div class="issue-content-right ui segment">
<strong>{{ctx.Locale.Tr "packages.details"}}</strong>
<div class="ui relaxed list">
- <div class="item">{{svg .PackageDescriptor.Package.Type.SVGName 16 "gt-mr-3"}} {{.PackageDescriptor.Package.Type.Name}}</div>
+ <div class="item">{{svg .PackageDescriptor.Package.Type.SVGName 16 "tw-mr-2"}} {{.PackageDescriptor.Package.Type.Name}}</div>
{{if .HasRepositoryAccess}}
- <div class="item">{{svg "octicon-repo" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Repository.Link}}">{{.PackageDescriptor.Repository.FullName}}</a></div>
+ <div class="item">{{svg "octicon-repo" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Repository.Link}}">{{.PackageDescriptor.Repository.FullName}}</a></div>
{{end}}
- <div class="item">{{svg "octicon-calendar" 16 "gt-mr-3"}} {{TimeSinceUnix .PackageDescriptor.Version.CreatedUnix ctx.Locale}}</div>
- <div class="item">{{svg "octicon-download" 16 "gt-mr-3"}} {{.PackageDescriptor.Version.DownloadCount}}</div>
+ <div class="item">{{svg "octicon-calendar" 16 "tw-mr-2"}} {{TimeSinceUnix .PackageDescriptor.Version.CreatedUnix ctx.Locale}}</div>
+ <div class="item">{{svg "octicon-download" 16 "tw-mr-2"}} {{.PackageDescriptor.Version.DownloadCount}}</div>
{{template "package/metadata/alpine" .}}
{{template "package/metadata/cargo" .}}
{{template "package/metadata/chef" .}}
@@ -70,7 +70,7 @@
{{template "package/metadata/swift" .}}
{{template "package/metadata/vagrant" .}}
{{if not (and (eq .PackageDescriptor.Package.Type "container") .PackageDescriptor.Metadata.Manifests)}}
- <div class="item">{{svg "octicon-database" 16 "gt-mr-3"}} {{FileSize .PackageDescriptor.CalculateBlobSize}}</div>
+ <div class="item">{{svg "octicon-database" 16 "tw-mr-2"}} {{FileSize .PackageDescriptor.CalculateBlobSize}}</div>
{{end}}
</div>
{{if not (eq .PackageDescriptor.Package.Type "container")}}
@@ -100,10 +100,10 @@
<div class="divider"></div>
<div class="ui relaxed list">
{{if .HasRepositoryAccess}}
- <div class="item">{{svg "octicon-issue-opened" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Repository.Link}}/issues">{{ctx.Locale.Tr "repo.issues"}}</a></div>
+ <div class="item">{{svg "octicon-issue-opened" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Repository.Link}}/issues">{{ctx.Locale.Tr "repo.issues"}}</a></div>
{{end}}
{{if .CanWritePackages}}
- <div class="item">{{svg "octicon-tools" 16 "gt-mr-3"}} <a href="{{.Link}}/settings">{{ctx.Locale.Tr "repo.settings"}}</a></div>
+ <div class="item">{{svg "octicon-tools" 16 "tw-mr-2"}} <a href="{{.Link}}/settings">{{ctx.Locale.Tr "repo.settings"}}</a></div>
{{end}}
</div>
{{end}}
diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl
index f33f9180bb..0908f5648c 100644
--- a/templates/projects/list.tmpl
+++ b/templates/projects/list.tmpl
@@ -1,12 +1,12 @@
{{if and $.CanWriteProjects (not $.Repository.IsArchived)}}
- <div class="tw-flex tw-justify-between gt-mb-4">
+ <div class="tw-flex tw-justify-between tw-mb-4">
<div class="small-menu-items ui compact tiny menu list-header-toggle">
<a class="item{{if not .IsShowClosed}} active{{end}}" href="?state=open&q={{$.Keyword}}">
- {{svg "octicon-project-symlink" 16 "gt-mr-3"}}
+ {{svg "octicon-project-symlink" 16 "tw-mr-2"}}
{{ctx.Locale.PrettyNumber .OpenCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.open_title"}}
</a>
<a class="item{{if .IsShowClosed}} active{{end}}" href="?state=closed&q={{$.Keyword}}">
- {{svg "octicon-check" 16 "gt-mr-3"}}
+ {{svg "octicon-check" 16 "tw-mr-2"}}
{{ctx.Locale.PrettyNumber .ClosedCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.closed_title"}}
</a>
</div>
@@ -25,7 +25,7 @@
{{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.project_kind")}}
</form>
<!-- Sort -->
- <div class="list-header-sort ui small dropdown type jump item">
+ <div class="list-header-sort ui dropdown type jump item">
<span class="text">
{{ctx.Locale.Tr "repo.issues.filter_sort"}}
</span>
@@ -41,7 +41,7 @@
<div class="milestone-list">
{{range .Projects}}
<li class="milestone-card">
- <h3 class="flex-text-block gt-m-0">
+ <h3 class="flex-text-block tw-m-0">
{{svg .IconName 16}}
<a class="muted" href="{{.Link ctx}}">{{.Title}}</a>
</h3>
diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl
index 93c2cdbb57..b45174b086 100644
--- a/templates/projects/view.tmpl
+++ b/templates/projects/view.tmpl
@@ -1,8 +1,8 @@
{{$canWriteProject := and .CanWriteProjects (or (not .Repository) (not .Repository.IsArchived))}}
<div class="ui container">
- <div class="tw-flex tw-justify-between tw-items-center gt-mb-4">
- <h2 class="gt-mb-0">{{.Project.Title}}</h2>
+ <div class="tw-flex tw-justify-between tw-items-center tw-mb-4">
+ <h2 class="tw-mb-0">{{.Project.Title}}</h2>
{{if $canWriteProject}}
<div class="ui compact mini menu">
<a class="item" href="{{.Link}}/edit?redirect=project">
@@ -67,16 +67,16 @@
<div class="board {{if .CanWriteProjects}}sortable{{end}}">
{{range .Columns}}
<div class="ui segment project-column" style="background: {{.Color}} !important;" data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.Link}}/{{.ID}}">
- <div class="project-column-header">
- <div class="ui large label project-column-title gt-py-2">
+ <div class="project-column-header{{if $canWriteProject}} tw-cursor-grab{{end}}">
+ <div class="ui large label project-column-title tw-py-1">
<div class="ui small circular grey label project-column-issue-count">
{{.NumIssues ctx}}
</div>
{{.Title}}
</div>
- {{if and $canWriteProject (ne .ID 0)}}
+ {{if $canWriteProject}}
<div class="ui dropdown jump item">
- <div class="gt-px-3">
+ <div class="tw-px-2">
{{svg "octicon-kebab-horizontal"}}
</div>
<div class="menu user-menu">
@@ -86,29 +86,20 @@
</a>
{{if not .Default}}
<a class="item show-modal button default-project-column-show"
- data-modal="#default-project-column-modal-{{.ID}}"
- data-modal-default-project-column-header="{{ctx.Locale.Tr "repo.projects.column.set_default"}}"
- data-modal-default-project-column-content="{{ctx.Locale.Tr "repo.projects.column.set_default_desc"}}"
- data-url="{{$.Link}}/{{.ID}}/default">
+ data-modal="#default-project-column-modal-{{.ID}}"
+ data-modal-default-project-column-header="{{ctx.Locale.Tr "repo.projects.column.set_default"}}"
+ data-modal-default-project-column-content="{{ctx.Locale.Tr "repo.projects.column.set_default_desc"}}"
+ data-url="{{$.Link}}/{{.ID}}/default">
{{svg "octicon-pin"}}
{{ctx.Locale.Tr "repo.projects.column.set_default"}}
</a>
- {{else}}
- <a class="item show-modal button default-project-column-show"
- data-modal="#default-project-column-modal-{{.ID}}"
- data-modal-default-project-column-header="{{ctx.Locale.Tr "repo.projects.column.unset_default"}}"
- data-modal-default-project-column-content="{{ctx.Locale.Tr "repo.projects.column.unset_default_desc"}}"
- data-url="{{$.Link}}/{{.ID}}/unsetdefault">
- {{svg "octicon-pin-slash"}}
- {{ctx.Locale.Tr "repo.projects.column.unset_default"}}
+ <a class="item show-modal button show-delete-project-column-modal"
+ data-modal="#delete-project-column-modal-{{.ID}}"
+ data-url="{{$.Link}}/{{.ID}}">
+ {{svg "octicon-trash"}}
+ {{ctx.Locale.Tr "repo.projects.column.delete"}}
</a>
{{end}}
- <a class="item show-modal button show-delete-project-column-modal"
- data-modal="#delete-project-column-modal-{{.ID}}"
- data-url="{{$.Link}}/{{.ID}}">
- {{svg "octicon-trash"}}
- {{ctx.Locale.Tr "repo.projects.column.delete"}}
- </a>
<div class="ui small modal edit-project-column-modal" id="edit-project-column-modal-{{.ID}}">
<div class="header">
@@ -165,7 +156,7 @@
<div class="divider"></div>
- <div class="ui cards {{if and $canWriteProject (ne .ID 0)}}{{/* ID 0 is default column which cannot be moved */}}tw-cursor-grab{{end}}" data-url="{{$.Link}}/{{.ID}}" data-project="{{$.Project.ID}}" data-board="{{.ID}}" id="board_{{.ID}}">
+ <div class="ui cards" data-url="{{$.Link}}/{{.ID}}" data-project="{{$.Project.ID}}" data-board="{{.ID}}" id="board_{{.ID}}">
{{range (index $.IssuesMap .ID)}}
<div class="issue-card gt-word-break {{if $canWriteProject}}tw-cursor-grab{{end}}" data-issue="{{.ID}}">
{{template "repo/issue/card" (dict "Issue" . "Page" $)}}
diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl
index 916949d4f9..b66d0e360a 100644
--- a/templates/repo/actions/list.tmpl
+++ b/templates/repo/actions/list.tmpl
@@ -66,7 +66,7 @@
</div>
{{if .AllowDisableOrEnableWorkflow}}
- <button class="ui jump dropdown btn interact-bg gt-p-3">
+ <button class="ui jump dropdown btn interact-bg tw-p-2">
{{svg "octicon-kebab-horizontal"}}
<div class="menu">
<a class="item link-action" data-url="{{$.Link}}/{{if .CurWorkflowDisabled}}enable{{else}}disable{{end}}?workflow={{$.CurWorkflow}}&actor={{.CurActor}}&status={{$.CurStatus}}">
diff --git a/templates/repo/actions/no_workflows.tmpl b/templates/repo/actions/no_workflows.tmpl
index 009313581e..88d6e513ef 100644
--- a/templates/repo/actions/no_workflows.tmpl
+++ b/templates/repo/actions/no_workflows.tmpl
@@ -2,7 +2,7 @@
{{svg "octicon-no-entry" 48}}
<h2>{{ctx.Locale.Tr "actions.runs.no_workflows"}}</h2>
{{if and .CanWriteCode .CanWriteActions}}
- <p>{{ctx.Locale.Tr "actions.runs.no_workflows.quick_start" "https://docs.gitea.com/usage/actions/quickstart/"}}</p>
+ <p>{{ctx.Locale.Tr "actions.runs.no_workflows.quick_start" "https://forgejo.org/docs/latest/admin/actions/"}}</p>
{{end}}
- <p>{{ctx.Locale.Tr "actions.runs.no_workflows.documentation" "https://docs.gitea.com/usage/actions/overview/"}}</p>
+ <p>{{ctx.Locale.Tr "actions.runs.no_workflows.documentation" "https://forgejo.org/docs/latest/admin/actions/"}}</p>
</div>
diff --git a/templates/repo/actions/runs_list.tmpl b/templates/repo/actions/runs_list.tmpl
index d393df6539..ac5049cf56 100644
--- a/templates/repo/actions/runs_list.tmpl
+++ b/templates/repo/actions/runs_list.tmpl
@@ -28,9 +28,9 @@
</div>
<div class="flex-item-trailing">
{{if .RefLink}}
- <a class="ui label gt-px-2 gt-mx-0" href="{{.RefLink}}">{{.PrettyRef}}</a>
+ <a class="ui label tw-px-1 tw-mx-0" href="{{.RefLink}}">{{.PrettyRef}}</a>
{{else}}
- <span class="ui label gt-px-2 gt-mx-0">{{.PrettyRef}}</span>
+ <span class="ui label tw-px-1 tw-mx-0">{{.PrettyRef}}</span>
{{end}}
</div>
<div class="run-list-item-right">
diff --git a/templates/repo/blame.tmpl b/templates/repo/blame.tmpl
index 67046b814c..45cf841748 100644
--- a/templates/repo/blame.tmpl
+++ b/templates/repo/blame.tmpl
@@ -12,7 +12,7 @@
{{end}}
<div class="{{TabSizeClass .Editorconfig .FileName}} non-diff-file-content">
<h4 class="file-header ui top attached header tw-flex tw-items-center tw-justify-between tw-flex-wrap">
- <div class="file-header-left tw-flex tw-items-center gt-py-3 gt-pr-4">
+ <div class="file-header-left tw-flex tw-items-center tw-py-2 tw-pr-4">
{{template "repo/file_info" .}}
</div>
<div class="file-header-right file-actions tw-flex tw-items-center tw-flex-wrap">
@@ -24,7 +24,7 @@
<a class="ui tiny button" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.normal_view"}}</a>
<a class="ui tiny button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.file_history"}}</a>
<button class="ui tiny button unescape-button">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
- <button class="ui tiny button escape-button gt-hidden">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
+ <button class="ui tiny button escape-button tw-hidden">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
</div>
</div>
</h4>
@@ -34,7 +34,7 @@
<table>
<tbody>
<tr>
- <td class="gt-text-center gt-p-1"><strong>{{ctx.Locale.Tr "repo.file_too_large"}}</strong></td>
+ <td class="gt-text-center tw-p-0.5"><strong>{{ctx.Locale.Tr "repo.file_too_large"}}</strong></td>
</tr>
</tbody>
</table>
@@ -50,11 +50,11 @@
{{$row.Avatar}}
</div>
<div class="blame-message">
- <a href="{{$row.CommitURL}}" title="{{$row.CommitMessage}}">
+ <a class="suppressed tw-text-text" href="{{$row.CommitURL}}" title="{{$row.CommitMessage}}">
{{$row.CommitMessage}}
</a>
</div>
- <div class="blame-time">
+ <div class="blame-time not-mobile">
{{$row.CommitSince}}
</div>
</div>
@@ -62,7 +62,7 @@
</td>
<td class="lines-blame-btn">
{{if $row.PreviousSha}}
- <a href="{{$row.PreviousShaURL}}" data-tooltip-content='{{ctx.Locale.Tr "repo.blame_prior"}}'>
+ <a role="button" class="muted" href="{{$row.PreviousShaURL}}" data-tooltip-content='{{ctx.Locale.Tr "repo.blame_prior"}}'>
{{svg "octicon-versions"}}
</a>
{{end}}
@@ -78,12 +78,18 @@
</td>
{{end}}
<td rel="L{{$row.RowNumber}}" class="lines-code blame-code chroma">
- <code class="code-inner gt-pl-3">{{$row.Code}}</code>
+ <code class="code-inner tw-pl-2">{{$row.Code}}</code>
</td>
</tr>
{{end}}
</tbody>
</table>
+ <div class="code-line-menu tippy-target">
+ {{if $.Permission.CanRead $.UnitTypeIssues}}
+ <a class="item ref-in-new-issue" role="menuitem" data-url-issue-new="{{.RepoLink}}/issues/new" data-url-param-body-link="{{.Repository.Link}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}{{if $.HasSourceRenderedToggle}}?display=source{{end}}" rel="nofollow noindex">{{ctx.Locale.Tr "repo.issues.context.reference_issue"}}</a>
+ {{end}}
+ <a class="item copy-line-permalink" role="menuitem" data-url="{{.Repository.Link}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}{{if $.HasSourceRenderedToggle}}?display=source{{end}}">{{ctx.Locale.Tr "repo.file_copy_permalink"}}</a>
+ </div>
{{end}}
</div>
</div>
diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl
index 7e061696e4..77cccd65b7 100644
--- a/templates/repo/branch/list.tmpl
+++ b/templates/repo/branch/list.tmpl
@@ -22,14 +22,14 @@
<div class="flex-text-block">
{{if .DefaultBranchBranch.IsProtected}}{{svg "octicon-shield-lock"}}{{end}}
<a class="gt-ellipsis" href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{.DefaultBranchBranch.DBBranch.Name}}</a>
- <button class="btn interact-fg gt-px-2" data-clipboard-text="{{.DefaultBranchBranch.DBBranch.Name}}">{{svg "octicon-copy" 14}}</button>
+ <button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DefaultBranchBranch.DBBranch.Name}}">{{svg "octicon-copy" 14}}</button>
{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DefaultBranchBranch.DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DefaultBranchBranch.DBBranch.CommitID)}}
</div>
- <p class="info tw-flex tw-items-center gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DefaultBranchBranch.DBBranch.CommitMessage (.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{TimeSince .DefaultBranchBranch.DBBranch.CommitTime.AsTime ctx.Locale}}{{if .DefaultBranchBranch.DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p>
+ <p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DefaultBranchBranch.DBBranch.CommitMessage (.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{TimeSince .DefaultBranchBranch.DBBranch.CommitTime.AsTime ctx.Locale}}{{if .DefaultBranchBranch.DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p>
</td>
<td class="right aligned middle aligned overflow-visible">
{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
- <button class="btn interact-bg show-create-branch-modal gt-p-3"
+ <button class="btn interact-bg show-create-branch-modal tw-p-2"
data-modal="#create-branch-modal"
data-branch-from="{{$.DefaultBranchBranch.DBBranch.Name}}"
data-branch-from-urlcomponent="{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}"
@@ -39,10 +39,10 @@
</button>
{{end}}
{{if .EnableFeed}}
- <a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{svg "octicon-rss"}}</a>
+ <a role="button" class="btn interact-bg tw-p-2" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{svg "octicon-rss"}}</a>
{{end}}
{{if not $.DisableDownloadSourceArchives}}
- <div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.download" ($.DefaultBranchBranch.DBBranch.Name)}}">
+ <div class="ui dropdown btn interact-bg tw-p-2" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.download" ($.DefaultBranchBranch.DBBranch.Name)}}">
{{svg "octicon-download"}}
<div class="menu">
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a>
@@ -51,7 +51,7 @@
</div>
{{end}}
{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted) (not $.IsMirror)}}
- <button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal"
+ <button class="btn interact-bg tw-p-2 show-modal show-rename-branch-modal"
data-is-default-branch="true"
data-modal="#rename-branch-modal"
data-old-branch-name="{{$.DefaultBranchBranch.DBBranch.Name}}"
@@ -88,17 +88,17 @@
{{if .DBBranch.IsDeleted}}
<div class="flex-text-block">
<a class="gt-ellipsis" href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a>
- <button class="btn interact-fg gt-px-2" data-clipboard-text="{{.DBBranch.Name}}">{{svg "octicon-copy" 14}}</button>
+ <button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}">{{svg "octicon-copy" 14}}</button>
</div>
<p class="info">{{ctx.Locale.Tr "repo.branch.deleted_by" .DBBranch.DeletedBy.Name}} {{TimeSinceUnix .DBBranch.DeletedUnix ctx.Locale}}</p>
{{else}}
<div class="flex-text-block">
{{if .IsProtected}}{{svg "octicon-shield-lock"}}{{end}}
<a class="gt-ellipsis" href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a>
- <button class="btn interact-fg gt-px-2" data-clipboard-text="{{.DBBranch.Name}}">{{svg "octicon-copy" 14}}</button>
+ <button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}">{{svg "octicon-copy" 14}}</button>
{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DBBranch.CommitID)}}
</div>
- <p class="info tw-flex tw-items-center gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DBBranch.CommitMessage ($.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{TimeSince .DBBranch.CommitTime.AsTime ctx.Locale}}{{if .DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} &nbsp;{{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p>
+ <p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DBBranch.CommitMessage ($.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{TimeSince .DBBranch.CommitTime.AsTime ctx.Locale}}{{if .DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} &nbsp;{{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p>
{{end}}
</td>
<td class="two wide ui">
@@ -124,29 +124,29 @@
</span>
{{else if and (not .DBBranch.IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
<a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{PathEscape $.Owner.Name}}:{{end}}{{PathEscapeSegments .DBBranch.Name}}">
- <button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{ctx.Locale.Tr "repo.pulls.compare_changes"}}{{else}}{{ctx.Locale.Tr "action.compare_branch"}}{{end}}</button>
+ <button id="new-pull-request" class="ui compact basic button tw-mr-0">{{if $.CanPull}}{{ctx.Locale.Tr "repo.pulls.compare_changes"}}{{else}}{{ctx.Locale.Tr "action.compare_branch"}}{{end}}</button>
</a>
{{end}}
{{else if and .LatestPullRequest.HasMerged .MergeMovedOn}}
{{if and (not .DBBranch.IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
<a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{PathEscape $.Owner.Name}}:{{end}}{{PathEscapeSegments .DBBranch.Name}}">
- <button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{ctx.Locale.Tr "repo.pulls.compare_changes"}}{{else}}{{ctx.Locale.Tr "action.compare_branch"}}{{end}}</button>
+ <button id="new-pull-request" class="ui compact basic button tw-mr-0">{{if $.CanPull}}{{ctx.Locale.Tr "repo.pulls.compare_changes"}}{{else}}{{ctx.Locale.Tr "action.compare_branch"}}{{end}}</button>
</a>
{{end}}
{{else}}
<a href="{{.LatestPullRequest.Issue.Link}}" class="tw-align-middle ref-issue">{{if not .LatestPullRequest.IsSameRepo}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a>
{{if .LatestPullRequest.HasMerged}}
- <a href="{{.LatestPullRequest.Issue.Link}}" class="ui purple large label">{{svg "octicon-git-merge" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.pulls.merged"}}</a>
+ <a href="{{.LatestPullRequest.Issue.Link}}" class="ui purple large label">{{svg "octicon-git-merge" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.pulls.merged"}}</a>
{{else if .LatestPullRequest.Issue.IsClosed}}
- <a href="{{.LatestPullRequest.Issue.Link}}" class="ui red large label">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.issues.closed_title"}}</a>
+ <a href="{{.LatestPullRequest.Issue.Link}}" class="ui red large label">{{svg "octicon-git-pull-request" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.issues.closed_title"}}</a>
{{else}}
- <a href="{{.LatestPullRequest.Issue.Link}}" class="ui green large label">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.issues.open_title"}}</a>
+ <a href="{{.LatestPullRequest.Issue.Link}}" class="ui green large label">{{svg "octicon-git-pull-request" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.issues.open_title"}}</a>
{{end}}
{{end}}
</td>
<td class="three wide right aligned overflow-visible">
{{if and $.IsWriter (not $.Repository.IsArchived) (not .DBBranch.IsDeleted)}}
- <button class="btn interact-bg gt-p-3 show-modal show-create-branch-modal"
+ <button class="btn interact-bg tw-p-2 show-modal show-create-branch-modal"
data-branch-from="{{.DBBranch.Name}}"
data-branch-from-urlcomponent="{{PathEscapeSegments .DBBranch.Name}}"
data-tooltip-content="{{ctx.Locale.Tr "repo.branch.new_branch_from" .DBBranch.Name}}"
@@ -156,10 +156,10 @@
</button>
{{end}}
{{if $.EnableFeed}}
- <a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DBBranch.Name}}">{{svg "octicon-rss"}}</a>
+ <a role="button" class="btn interact-bg tw-p-2" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DBBranch.Name}}">{{svg "octicon-rss"}}</a>
{{end}}
{{if and (not .DBBranch.IsDeleted) (not $.DisableDownloadSourceArchives)}}
- <div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.download" (.DBBranch.Name)}}">
+ <div class="ui dropdown btn interact-bg tw-p-2" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.download" (.DBBranch.Name)}}">
{{svg "octicon-download"}}
<div class="menu">
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .DBBranch.Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a>
@@ -168,7 +168,7 @@
</div>
{{end}}
{{if and $.IsWriter (not $.Repository.IsArchived) (not .DBBranch.IsDeleted) (not $.IsMirror)}}
- <button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal"
+ <button class="btn interact-bg tw-p-2 show-modal show-rename-branch-modal"
data-is-default-branch="false"
data-old-branch-name="{{.DBBranch.Name}}"
data-modal="#rename-branch-modal"
@@ -179,13 +179,13 @@
{{end}}
{{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}}
{{if .DBBranch.IsDeleted}}
- <button class="btn interact-bg gt-p-3 link-action restore-branch-button" data-url="{{$.Link}}/restore?branch_id={{.DBBranch.ID}}&name={{.DBBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.restore" (.DBBranch.Name)}}">
+ <button class="btn interact-bg tw-p-2 link-action restore-branch-button" data-url="{{$.Link}}/restore?branch_id={{.DBBranch.ID}}&name={{.DBBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.restore" (.DBBranch.Name)}}">
<span class="text blue">
{{svg "octicon-reply"}}
</span>
</button>
{{else}}
- <button class="btn interact-bg gt-p-3 delete-button delete-branch-button" data-url="{{$.Link}}/delete?name={{.DBBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.delete" (.DBBranch.Name)}}" data-name="{{.DBBranch.Name}}">
+ <button class="btn interact-bg tw-p-2 delete-button delete-branch-button" data-url="{{$.Link}}/delete?name={{.DBBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.delete" (.DBBranch.Name)}}" data-name="{{.DBBranch.Name}}">
{{svg "octicon-trash"}}
</button>
{{end}}
diff --git a/templates/repo/branch_dropdown.tmpl b/templates/repo/branch_dropdown.tmpl
index e389a3c758..707f670e97 100644
--- a/templates/repo/branch_dropdown.tmpl
+++ b/templates/repo/branch_dropdown.tmpl
@@ -70,8 +70,8 @@
<div class="js-branch-tag-selector {{if .ContainerClasses}}{{.ContainerClasses}}{{end}}">
{{/* show dummy elements before Vue componment is mounted, this code must match the code in BranchTagSelector.vue */}}
<div class="ui dropdown custom">
- <button class="branch-dropdown-button gt-ellipsis ui basic small compact button tw-flex gt-m-0">
- <span class="text tw-flex tw-items-center gt-mr-2">
+ <button class="branch-dropdown-button gt-ellipsis ui basic small compact button tw-flex tw-m-0">
+ <span class="text tw-flex tw-items-center tw-mr-1">
{{if .release}}
{{ctx.Locale.Tr "repo.release.compare"}}
{{else}}
@@ -80,7 +80,7 @@
{{else}}
{{svg "octicon-git-branch"}}
{{end}}
- <strong ref="dropdownRefName" class="gt-ml-3">{{if and .root.IsViewTag (not .noTag)}}{{.root.TagName}}{{else if .root.IsViewBranch}}{{.root.BranchName}}{{else}}{{ShortSha .root.CommitID}}{{end}}</strong>
+ <strong ref="dropdownRefName" class="tw-ml-2">{{if and .root.IsViewTag (not .noTag)}}{{.root.TagName}}{{else if .root.IsViewBranch}}{{.root.BranchName}}{{else}}{{ShortSha .root.CommitID}}{{end}}</strong>
{{end}}
</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
diff --git a/templates/repo/code/recently_pushed_new_branches.tmpl b/templates/repo/code/recently_pushed_new_branches.tmpl
index 8dd401d283..f6f595a00f 100644
--- a/templates/repo/code/recently_pushed_new_branches.tmpl
+++ b/templates/repo/code/recently_pushed_new_branches.tmpl
@@ -10,7 +10,7 @@
{{$branchLink := (print ($repo.Link) "/src/branch/" (PathEscapeSegments .Name))}}
{{ctx.Locale.Tr "repo.pulls.recently_pushed_new_branches" $name $timeSince $branchLink}}
</div>
- <a role="button" class="ui compact positive button gt-m-0" href="{{$.Repository.ComposeBranchCompareURL $.Repository.BaseRepo $name}}">
+ <a role="button" class="ui compact positive button tw-m-0" href="{{$.Repository.ComposeBranchCompareURL $.Repository.BaseRepo $name}}">
{{ctx.Locale.Tr "repo.pulls.compare_changes"}}
</a>
</div>
diff --git a/templates/repo/commit_load_branches_and_tags.tmpl b/templates/repo/commit_load_branches_and_tags.tmpl
index 49f7323845..ffa0e530e8 100644
--- a/templates/repo/commit_load_branches_and_tags.tmpl
+++ b/templates/repo/commit_load_branches_and_tags.tmpl
@@ -1,18 +1,18 @@
{{if not .PageIsWiki}}
<div class="branch-and-tag-area" data-text-default-branch-tooltip="{{ctx.Locale.Tr "repo.commit.contained_in_default_branch"}}">
- <button class="ui button ellipsis-button load-branches-and-tags gt-mt-3" aria-expanded="false"
+ <button class="ui button ellipsis-button load-branches-and-tags tw-mt-2" aria-expanded="false"
data-fetch-url="{{.RepoLink}}/commit/{{.CommitID}}/load-branches-and-tags"
data-tooltip-content="{{ctx.Locale.Tr "repo.commit.load_referencing_branches_and_tags"}}"
>...</button>
- <div class="branch-and-tag-detail gt-hidden">
+ <div class="branch-and-tag-detail tw-hidden">
<div class="divider"></div>
<div>{{ctx.Locale.Tr "repo.commit.contained_in"}}</div>
- <div class="tw-flex gt-mt-3">
- <div class="gt-p-2">{{svg "octicon-git-branch"}}</div>
+ <div class="tw-flex tw-mt-2">
+ <div class="tw-p-1">{{svg "octicon-git-branch"}}</div>
<div class="branch-area flex-text-block tw-flex-wrap tw-flex-1"></div>
</div>
- <div class="tw-flex gt-mt-3">
- <div class="gt-p-2">{{svg "octicon-tag"}}</div>
+ <div class="tw-flex tw-mt-2">
+ <div class="tw-p-1">{{svg "octicon-tag"}}</div>
<div class="tag-area flex-text-block tw-flex-wrap tw-flex-1"></div>
</div>
</div>
diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl
index 345c28f475..3ae7fffa1c 100644
--- a/templates/repo/commit_page.tmpl
+++ b/templates/repo/commit_page.tmpl
@@ -18,8 +18,8 @@
{{end}}
{{end}}
<div class="ui top attached header clearing segment tw-relative commit-header {{$class}}">
- <div class="tw-flex gt-mb-4 tw-flex-wrap">
- <h3 class="gt-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3>
+ <div class="tw-flex tw-mb-4 tw-flex-wrap">
+ <h3 class="tw-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3>
{{if not $.PageIsWiki}}
<div>
<a class="ui primary tiny button" href="{{.SourcePath}}">
@@ -139,27 +139,27 @@
{{end}}
{{template "repo/commit_load_branches_and_tags" .}}
</div>
- <div class="ui attached segment tw-flex tw-items-center tw-justify-between gt-py-2 commit-header-row tw-flex-wrap {{$class}}">
+ <div class="ui attached segment tw-flex tw-items-center tw-justify-between tw-py-1 commit-header-row tw-flex-wrap {{$class}}">
<div class="tw-flex tw-items-center author">
{{if .Author}}
- {{ctx.AvatarUtils.Avatar .Author 28 "gt-mr-3"}}
+ {{ctx.AvatarUtils.Avatar .Author 28 "tw-mr-2"}}
{{if .Author.FullName}}
<a href="{{.Author.HomeLink}}"><strong>{{.Author.FullName}}</strong></a>
{{else}}
<a href="{{.Author.HomeLink}}"><strong>{{.Commit.Author.Name}}</strong></a>
{{end}}
{{else}}
- {{ctx.AvatarUtils.AvatarByEmail .Commit.Author.Email .Commit.Author.Email 28 "gt-mr-3"}}
+ {{ctx.AvatarUtils.AvatarByEmail .Commit.Author.Email .Commit.Author.Email 28 "tw-mr-2"}}
<strong>{{.Commit.Author.Name}}</strong>
{{end}}
- <span class="text grey gt-ml-3" id="authored-time">{{TimeSince .Commit.Author.When ctx.Locale}}</span>
+ <span class="text grey tw-ml-2" id="authored-time">{{TimeSince .Commit.Author.When ctx.Locale}}</span>
{{if or (ne .Commit.Committer.Name .Commit.Author.Name) (ne .Commit.Committer.Email .Commit.Author.Email)}}
- <span class="text grey gt-mx-3">{{ctx.Locale.Tr "repo.diff.committed_by"}}</span>
+ <span class="text grey tw-mx-2">{{ctx.Locale.Tr "repo.diff.committed_by"}}</span>
{{if ne .Verification.CommittingUser.ID 0}}
- {{ctx.AvatarUtils.Avatar .Verification.CommittingUser 28 "gt-mx-3"}}
+ {{ctx.AvatarUtils.Avatar .Verification.CommittingUser 28 "tw-mx-2"}}
<a href="{{.Verification.CommittingUser.HomeLink}}"><strong>{{.Commit.Committer.Name}}</strong></a>
{{else}}
- {{ctx.AvatarUtils.AvatarByEmail .Commit.Committer.Email .Commit.Committer.Name 28 "gt-mr-3"}}
+ {{ctx.AvatarUtils.AvatarByEmail .Commit.Committer.Email .Commit.Committer.Name 28 "tw-mr-2"}}
<strong>{{.Commit.Committer.Name}}</strong>
{{end}}
{{end}}
@@ -184,73 +184,73 @@
</div>
</div>
{{if .Commit.Signature}}
- <div class="ui bottom attached message tw-text-left tw-flex tw-items-center tw-justify-between commit-header-row tw-flex-wrap gt-mb-0 {{$class}}">
+ <div class="ui bottom attached message tw-text-left tw-flex tw-items-center tw-justify-between commit-header-row tw-flex-wrap tw-mb-0 {{$class}}">
<div class="tw-flex tw-items-center">
{{if .Verification.Verified}}
{{if ne .Verification.SigningUser.ID 0}}
- {{svg "gitea-lock" 16 "gt-mr-3"}}
+ {{svg "gitea-lock" 16 "tw-mr-2"}}
{{if eq .Verification.TrustStatus "trusted"}}
- <span class="ui text gt-mr-3">{{ctx.Locale.Tr "repo.commits.signed_by"}}:</span>
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by"}}:</span>
{{else if eq .Verification.TrustStatus "untrusted"}}
- <span class="ui text gt-mr-3">{{ctx.Locale.Tr "repo.commits.signed_by_untrusted_user"}}:</span>
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by_untrusted_user"}}:</span>
{{else}}
- <span class="ui text gt-mr-3">{{ctx.Locale.Tr "repo.commits.signed_by_untrusted_user_unmatched"}}:</span>
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by_untrusted_user_unmatched"}}:</span>
{{end}}
- {{ctx.AvatarUtils.Avatar .Verification.SigningUser 28 "gt-mr-3"}}
+ {{ctx.AvatarUtils.Avatar .Verification.SigningUser 28 "tw-mr-2"}}
<a href="{{.Verification.SigningUser.HomeLink}}"><strong>{{.Verification.SigningUser.GetDisplayName}}</strong></a>
{{else}}
- <span title="{{ctx.Locale.Tr "gpg.default_key"}}">{{svg "gitea-lock-cog" 16 "gt-mr-3"}}</span>
- <span class="ui text gt-mr-3">{{ctx.Locale.Tr "repo.commits.signed_by"}}:</span>
- {{ctx.AvatarUtils.AvatarByEmail .Verification.SigningEmail "" 28 "gt-mr-3"}}
+ <span title="{{ctx.Locale.Tr "gpg.default_key"}}">{{svg "gitea-lock-cog" 16 "tw-mr-2"}}</span>
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by"}}:</span>
+ {{ctx.AvatarUtils.AvatarByEmail .Verification.SigningEmail "" 28 "tw-mr-2"}}
<strong>{{.Verification.SigningUser.GetDisplayName}}</strong>
{{end}}
{{else}}
- {{svg "gitea-unlock" 16 "gt-mr-3"}}
+ {{svg "gitea-unlock" 16 "tw-mr-2"}}
<span class="ui text">{{ctx.Locale.Tr .Verification.Reason}}</span>
{{end}}
</div>
<div class="tw-flex tw-items-center">
{{if .Verification.Verified}}
{{if ne .Verification.SigningUser.ID 0}}
- {{svg "octicon-verified" 16 "gt-mr-3"}}
+ {{svg "octicon-verified" 16 "tw-mr-2"}}
{{if .Verification.SigningSSHKey}}
- <span class="ui text gt-mr-3">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
{{.Verification.SigningSSHKey.Fingerprint}}
{{else}}
- <span class="ui text gt-mr-3">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}}
{{end}}
{{else}}
- {{svg "octicon-unverified" 16 "gt-mr-3"}}
+ {{svg "octicon-unverified" 16 "tw-mr-2"}}
{{if .Verification.SigningSSHKey}}
- <span class="ui text gt-mr-3" data-tooltip-content="{{ctx.Locale.Tr "gpg.default_key"}}">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
+ <span class="ui text tw-mr-2" data-tooltip-content="{{ctx.Locale.Tr "gpg.default_key"}}">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
{{.Verification.SigningSSHKey.Fingerprint}}
{{else}}
- <span class="ui text gt-mr-3" data-tooltip-content="{{ctx.Locale.Tr "gpg.default_key"}}">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
+ <span class="ui text tw-mr-2" data-tooltip-content="{{ctx.Locale.Tr "gpg.default_key"}}">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}}
{{end}}
{{end}}
{{else if .Verification.Warning}}
- {{svg "octicon-unverified" 16 "gt-mr-3"}}
+ {{svg "octicon-unverified" 16 "tw-mr-2"}}
{{if .Verification.SigningSSHKey}}
- <span class="ui text gt-mr-3">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
{{.Verification.SigningSSHKey.Fingerprint}}
{{else}}
- <span class="ui text gt-mr-3">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}}
{{end}}
{{else}}
{{if .Verification.SigningKey}}
{{if ne .Verification.SigningKey.KeyID ""}}
- {{svg "octicon-verified" 16 "gt-mr-3"}}
- <span class="ui text gt-mr-3">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
+ {{svg "octicon-verified" 16 "tw-mr-2"}}
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}}
{{end}}
{{end}}
{{if .Verification.SigningSSHKey}}
{{if ne .Verification.SigningSSHKey.Fingerprint ""}}
- {{svg "octicon-verified" 16 "gt-mr-3"}}
- <span class="ui text gt-mr-3">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
+ {{svg "octicon-verified" 16 "tw-mr-2"}}
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
{{.Verification.SigningSSHKey.Fingerprint}}
{{end}}
{{end}}
@@ -260,7 +260,7 @@
{{end}}
{{if .NoteRendered}}
<div class="ui top attached header segment git-notes">
- {{svg "octicon-note" 16 "gt-mr-3"}}
+ {{svg "octicon-note" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.diff.git-notes"}}:
{{if .NoteAuthor}}
<a href="{{.NoteAuthor.HomeLink}}">
diff --git a/templates/repo/commits.tmpl b/templates/repo/commits.tmpl
index 1263bc9dd4..86df88f6e3 100644
--- a/templates/repo/commits.tmpl
+++ b/templates/repo/commits.tmpl
@@ -5,7 +5,7 @@
{{template "repo/sub_menu" .}}
<div class="repo-button-row">
<div class="tw-flex tw-items-center">
- {{template "repo/branch_dropdown" dict "root" . "ContainerClasses" "gt-mr-2"}}
+ {{template "repo/branch_dropdown" dict "root" . "ContainerClasses" "tw-mr-1"}}
<a href="{{.RepoLink}}/graph" class="ui basic small compact button">
{{svg "octicon-git-branch"}}
{{ctx.Locale.Tr "repo.commit_graph"}}
diff --git a/templates/repo/commits_list.tmpl b/templates/repo/commits_list.tmpl
index 4eb31e0e8e..bae9924141 100644
--- a/templates/repo/commits_list.tmpl
+++ b/templates/repo/commits_list.tmpl
@@ -13,16 +13,16 @@
{{$commitRepoLink := $.RepoLink}}{{if $.CommitRepoLink}}{{$commitRepoLink = $.CommitRepoLink}}{{end}}
{{range .Commits}}
<tr>
- <td class="author">
+ <td class="author tw-flex">
{{$userName := .Author.Name}}
{{if .User}}
{{if .User.FullName}}
{{$userName = .User.FullName}}
{{end}}
- {{ctx.AvatarUtils.Avatar .User 28 "gt-mr-2"}}<a href="{{.User.HomeLink}}">{{$userName}}</a>
+ {{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}<a class="muted author-wrapper" href="{{.User.HomeLink}}">{{$userName}}</a>
{{else}}
- {{ctx.AvatarUtils.AvatarByEmail .Author.Email .Author.Name 28 "gt-mr-2"}}
- {{$userName}}
+ {{ctx.AvatarUtils.AvatarByEmail .Author.Email .Author.Name 28 "tw-mr-2"}}
+ <span class="author-wrapper">{{$userName}}</span>
{{end}}
</td>
<td class="sha">
@@ -68,7 +68,7 @@
{{end}}
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}}
{{if IsMultilineCommitMessage .Message}}
- <pre class="commit-body gt-hidden">{{RenderCommitBody $.Context .Message ($.Repository.ComposeMetas ctx)}}</pre>
+ <pre class="commit-body tw-hidden">{{RenderCommitBody $.Context .Message ($.Repository.ComposeMetas ctx)}}</pre>
{{end}}
</td>
{{if .Committer}}
@@ -76,10 +76,10 @@
{{else}}
<td class="text right aligned">{{TimeSince .Author.When ctx.Locale}}</td>
{{end}}
- <td class="text right aligned gt-py-0">
- <button class="btn interact-bg gt-p-3" data-tooltip-content="{{ctx.Locale.Tr "copy_hash"}}" data-clipboard-text="{{.ID}}">{{svg "octicon-copy"}}</button>
+ <td class="text right aligned tw-py-0">
+ <button class="btn interact-bg tw-p-2" data-tooltip-content="{{ctx.Locale.Tr "copy_hash"}}" data-clipboard-text="{{.ID}}">{{svg "octicon-copy"}}</button>
<a
- class="btn interact-bg gt-p-3"
+ class="btn interact-bg tw-p-2"
data-tooltip-content="{{ctx.Locale.Tr "repo.commits.view_path"}}"
href="{{if $.FileName}}{{printf "%s/src/commit/%s/%s" $commitRepoLink (PathEscape .ID.String) (PathEscapeSegments $.FileName)}}{{else}}{{printf "%s/src/commit/%s" $commitRepoLink (PathEscape .ID.String)}}{{end}}">
{{svg "octicon-file-code"}}
diff --git a/templates/repo/commits_list_small.tmpl b/templates/repo/commits_list_small.tmpl
index b195f06483..d96b314d01 100644
--- a/templates/repo/commits_list_small.tmpl
+++ b/templates/repo/commits_list_small.tmpl
@@ -30,7 +30,7 @@
{{$class = (print $class " isWarning")}}
{{end}}
{{end}}
- <a href="{{$commitLink}}" rel="nofollow" class="gt-ml-3 {{$class}}">
+ <a href="{{$commitLink}}" rel="nofollow" class="tw-ml-2 {{$class}}">
<span class="shortsha">{{ShortSha .ID.String}}</span>
{{if .Signature}}
{{template "repo/shabox_badge" dict "root" $.root "verification" .Verification}}
@@ -38,12 +38,12 @@
</a>
</span>
- <span class="gt-mono commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject $.root.Context .Message $commitLink ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx)}}</span>
+ <span class="tw-font-mono commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject $.root.Context .Message $commitLink ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx)}}</span>
{{if IsMultilineCommitMessage .Message}}
<button class="ui button js-toggle-commit-body ellipsis-button" aria-expanded="false">...</button>
{{end}}
{{if IsMultilineCommitMessage .Message}}
- <pre class="commit-body gt-hidden">{{RenderCommitBody $.root.Context .Message ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx)}}</pre>
+ <pre class="commit-body tw-hidden">{{RenderCommitBody $.root.Context .Message ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx)}}</pre>
{{end}}
</div>
{{end}}
diff --git a/templates/repo/commits_table.tmpl b/templates/repo/commits_table.tmpl
index f04dedd49d..7249becbab 100644
--- a/templates/repo/commits_table.tmpl
+++ b/templates/repo/commits_table.tmpl
@@ -10,9 +10,9 @@
</div>
{{if .IsDiffCompare}}
<div class="commits-table-right tw-whitespace-nowrap">
- <a href="{{$.CommitRepoLink}}/commit/{{.BeforeCommitID | PathEscape}}" class="ui green sha label gt-mx-0">{{if not .BaseIsCommit}}{{if .BaseIsBranch}}{{svg "octicon-git-branch"}}{{else if .BaseIsTag}}{{svg "octicon-tag"}}{{end}}{{.BaseBranch}}{{else}}{{ShortSha .BaseBranch}}{{end}}</a>
+ <a href="{{$.CommitRepoLink}}/commit/{{.BeforeCommitID | PathEscape}}" class="ui green sha label tw-mx-0">{{if not .BaseIsCommit}}{{if .BaseIsBranch}}{{svg "octicon-git-branch"}}{{else if .BaseIsTag}}{{svg "octicon-tag"}}{{end}}{{.BaseBranch}}{{else}}{{ShortSha .BaseBranch}}{{end}}</a>
...
- <a href="{{$.CommitRepoLink}}/commit/{{.AfterCommitID | PathEscape}}" class="ui green sha label gt-mx-0">{{if not .HeadIsCommit}}{{if .HeadIsBranch}}{{svg "octicon-git-branch"}}{{else if .HeadIsTag}}{{svg "octicon-tag"}}{{end}}{{.HeadBranch}}{{else}}{{ShortSha .HeadBranch}}{{end}}</a>
+ <a href="{{$.CommitRepoLink}}/commit/{{.AfterCommitID | PathEscape}}" class="ui green sha label tw-mx-0">{{if not .HeadIsCommit}}{{if .HeadIsBranch}}{{svg "octicon-git-branch"}}{{else if .HeadIsTag}}{{svg "octicon-tag"}}{{end}}{{.HeadBranch}}{{else}}{{ShortSha .HeadBranch}}{{end}}</a>
</div>
{{end}}
</h4>
diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl
index 73c9ca6a1f..bcd3c16b6a 100644
--- a/templates/repo/create.tmpl
+++ b/templates/repo/create.tmpl
@@ -73,7 +73,7 @@
</div>
</div>
- <div id="template_units" class="gt-hidden">
+ <div id="template_units" class="tw-hidden">
<div class="inline field">
<label>{{ctx.Locale.Tr "repo.template.items"}}</label>
<div class="ui checkbox">
diff --git a/templates/repo/diff/blob_excerpt.tmpl b/templates/repo/diff/blob_excerpt.tmpl
index 201bff805a..8312b5d913 100644
--- a/templates/repo/diff/blob_excerpt.tmpl
+++ b/templates/repo/diff/blob_excerpt.tmpl
@@ -27,7 +27,7 @@
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$.FileNameHash}}L{{$line.LeftIdx}}{{end}}"></span></td>
<td class="blob-excerpt lines-escape lines-escape-old">{{if and $line.LeftIdx $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
- <td class="blob-excerpt lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="gt-mono" data-type-marker=""></span>{{end}}</td>
+ <td class="blob-excerpt lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="tw-font-mono" data-type-marker=""></span>{{end}}</td>
<td class="blob-excerpt lines-code lines-code-old">{{/*
*/}}{{if $line.LeftIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}}{{else}}{{/*
*/}}<code class="code-inner"></code>{{/*
@@ -35,7 +35,7 @@
*/}}</td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$.FileNameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
<td class="blob-excerpt lines-escape lines-escape-new">{{if and $line.RightIdx $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
- <td class="blob-excerpt lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="gt-mono" data-type-marker=""></span>{{end}}</td>
+ <td class="blob-excerpt lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="tw-font-mono" data-type-marker=""></span>{{end}}</td>
<td class="blob-excerpt lines-code lines-code-new">{{/*
*/}}{{if $line.RightIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}}{{else}}{{/*
*/}}<code class="code-inner"></code>{{/*
@@ -73,7 +73,7 @@
{{end}}
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
<td class="blob-excerpt lines-escape">{{if $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
- <td class="blob-excerpt lines-type-marker"><span class="gt-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
+ <td class="blob-excerpt lines-type-marker"><span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
<td class="blob-excerpt lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}"><code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code></td>
</tr>
{{end}}
diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl
index 672d1254d0..9ede83dcb7 100644
--- a/templates/repo/diff/box.tmpl
+++ b/templates/repo/diff/box.tmpl
@@ -1,31 +1,31 @@
{{$showFileTree := (and (not .DiffNotAvailable) (gt .Diff.NumFiles 1))}}
<div>
<div class="diff-detail-box diff-box">
- <div class="tw-flex tw-items-center tw-flex-wrap tw-gap-2 gt-ml-1">
+ <div class="tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-ml-0.5">
{{if $showFileTree}}
<button class="diff-toggle-file-tree-button not-mobile btn interact-fg" data-show-text="{{ctx.Locale.Tr "repo.diff.show_file_tree"}}" data-hide-text="{{ctx.Locale.Tr "repo.diff.hide_file_tree"}}">
{{/* the icon meaning is reversed here, "octicon-sidebar-collapse" means show the file tree */}}
- {{svg "octicon-sidebar-collapse" 20 "icon gt-hidden"}}
- {{svg "octicon-sidebar-expand" 20 "icon gt-hidden"}}
+ {{svg "octicon-sidebar-collapse" 20 "icon tw-hidden"}}
+ {{svg "octicon-sidebar-expand" 20 "icon tw-hidden"}}
</button>
<script>
// Default to true if unset
const diffTreeVisible = localStorage?.getItem('diff_file_tree_visible') !== 'false';
const diffTreeBtn = document.querySelector('.diff-toggle-file-tree-button');
const diffTreeIcon = `.octicon-sidebar-${diffTreeVisible ? 'expand' : 'collapse'}`;
- diffTreeBtn.querySelector(diffTreeIcon).classList.remove('gt-hidden');
+ diffTreeBtn.querySelector(diffTreeIcon).classList.remove('tw-hidden');
diffTreeBtn.setAttribute('data-tooltip-content', diffTreeBtn.getAttribute(diffTreeVisible ? 'data-hide-text' : 'data-show-text'));
</script>
{{end}}
{{if not .DiffNotAvailable}}
<div class="diff-detail-stats tw-flex tw-items-center tw-flex-wrap">
- {{svg "octicon-diff" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion}}
+ {{svg "octicon-diff" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion}}
</div>
{{end}}
</div>
<div class="diff-detail-actions">
{{if and .PageIsPullFiles $.SignedUserID (not .IsArchived) (not .DiffNotAvailable)}}
- <div class="not-mobile tw-flex tw-items-center tw-flex-col tw-whitespace-nowrap gt-mr-2">
+ <div class="not-mobile tw-flex tw-items-center tw-flex-col tw-whitespace-nowrap tw-mr-1">
<label for="viewed-files-summary" id="viewed-files-summary-label" data-text-changed-template="{{ctx.Locale.Tr "repo.pulls.viewed_files_label"}}">
{{ctx.Locale.Tr "repo.pulls.viewed_files_label" .Diff.NumViewedFiles .Diff.NumFiles}}
</label>
@@ -89,9 +89,9 @@
{{end}}
<div id="diff-container">
{{if $showFileTree}}
- <div id="diff-file-tree" class="gt-hidden not-mobile"></div>
+ <div id="diff-file-tree" class="tw-hidden not-mobile"></div>
<script>
- if (diffTreeVisible) document.getElementById('diff-file-tree').classList.remove('gt-hidden');
+ if (diffTreeVisible) document.getElementById('diff-file-tree').classList.remove('tw-hidden');
</script>
{{end}}
{{if .DiffNotAvailable}}
@@ -109,27 +109,27 @@
{{$showFileViewToggle := or $isImage (and (not $file.IsIncomplete) $isCsv)}}
{{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}}
{{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.IsArchived) $.IsShowingAllCommits}}
- <div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}} gt-mt-0" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}>
- <h4 class="diff-file-header sticky-2nd-row ui top attached normal header tw-flex tw-items-center tw-justify-between tw-flex-wrap">
- <div class="diff-file-name tw-flex tw-items-center tw-gap-1 tw-flex-wrap">
- <button class="fold-file btn interact-bg gt-p-2{{if not $isExpandable}} tw-invisible{{end}}">
+ <div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}} tw-mt-0" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}>
+ <h4 class="diff-file-header sticky-2nd-row ui top attached header tw-font-normal tw-flex tw-items-center tw-justify-between tw-flex-wrap">
+ <div class="diff-file-name tw-flex tw-items-center gt-gap-2 tw-flex-wrap">
+ <button class="fold-file btn interact-bg tw-p-1{{if not $isExpandable}} tw-invisible{{end}}">
{{if $file.ShouldBeHidden}}
{{svg "octicon-chevron-right" 18}}
{{else}}
{{svg "octicon-chevron-down" 18}}
{{end}}
</button>
- <div class="tw-font-semibold tw-flex tw-items-center gt-mono">
+ <div class="tw-font-semibold tw-flex tw-items-center tw-font-mono">
{{if $file.IsBin}}
- <span class="gt-ml-1 gt-mr-3">
+ <span class="tw-ml-0.5 tw-mr-2">
{{ctx.Locale.Tr "repo.diff.bin"}}
</span>
{{else}}
{{template "repo/diff/stats" dict "file" . "root" $}}
{{end}}
</div>
- <span class="file gt-mono"><a class="muted file-link" title="{{if $file.IsRenamed}}{{$file.OldName}} → {{end}}{{$file.Name}}" href="#diff-{{$file.NameHash}}">{{if $file.IsRenamed}}{{$file.OldName}} → {{end}}{{$file.Name}}</a>{{if .IsLFSFile}} ({{ctx.Locale.Tr "repo.stored_lfs"}}){{end}}</span>
- <button class="btn interact-fg gt-p-3" data-clipboard-text="{{$file.Name}}">{{svg "octicon-copy" 14}}</button>
+ <span class="file tw-font-mono"><a class="muted file-link" title="{{if $file.IsRenamed}}{{$file.OldName}} → {{end}}{{$file.Name}}" href="#diff-{{$file.NameHash}}">{{if $file.IsRenamed}}{{$file.OldName}} → {{end}}{{$file.Name}}</a>{{if .IsLFSFile}} ({{ctx.Locale.Tr "repo.stored_lfs"}}){{end}}</span>
+ <button class="btn interact-fg tw-p-2" data-clipboard-text="{{$file.Name}}">{{svg "octicon-copy" 14}}</button>
{{if $file.IsGenerated}}
<span class="ui label">{{ctx.Locale.Tr "repo.diff.generated"}}</span>
{{end}}
@@ -139,9 +139,9 @@
{{if and $file.Mode $file.OldMode}}
{{$old := ctx.Locale.Tr ($file.ModeTranslationKey $file.OldMode)}}
{{$new := ctx.Locale.Tr ($file.ModeTranslationKey $file.Mode)}}
- <span class="gt-ml-4 gt-mono">{{ctx.Locale.Tr "git.filemode.changed_filemode" $old $new}}</span>
+ <span class="tw-ml-4 tw-font-mono">{{ctx.Locale.Tr "git.filemode.changed_filemode" $old $new}}</span>
{{else if $file.Mode}}
- <span class="gt-ml-4 gt-mono">{{ctx.Locale.Tr ($file.ModeTranslationKey $file.Mode)}}</span>
+ <span class="tw-ml-4 tw-font-mono">{{ctx.Locale.Tr ($file.ModeTranslationKey $file.Mode)}}</span>
{{end}}
</div>
<div class="diff-file-header-actions tw-flex tw-items-center tw-gap-1 tw-flex-wrap">
@@ -159,7 +159,7 @@
{{end}}
{{if not (or $file.IsIncomplete $file.IsBin $file.IsSubmodule)}}
<button class="ui basic tiny button unescape-button not-mobile">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
- <button class="ui basic tiny button escape-button gt-hidden">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
+ <button class="ui basic tiny button escape-button tw-hidden">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
{{end}}
{{if and (not $file.IsSubmodule) (not $.PageIsWiki)}}
{{if $file.IsDeleted}}
@@ -179,7 +179,7 @@
</div>
</h4>
<div class="diff-file-body ui attached unstackable table segment" {{if and $file.IsViewed $.IsShowingAllCommits}}data-folded="true"{{end}}>
- <div id="diff-source-{{$file.NameHash}}" class="file-body file-code unicode-escaped code-diff{{if $.IsSplitStyle}} code-diff-split{{else}} code-diff-unified{{end}}{{if $showFileViewToggle}} gt-hidden{{end}}">
+ <div id="diff-source-{{$file.NameHash}}" class="file-body file-code unicode-escaped code-diff{{if $.IsSplitStyle}} code-diff-split{{else}} code-diff-unified{{end}}{{if $showFileViewToggle}} tw-hidden{{end}}">
{{if or $file.IsIncomplete $file.IsBin}}
<div class="diff-file-body binary">
{{if $file.IsIncomplete}}
@@ -220,8 +220,8 @@
{{end}}
{{if .Diff.IsIncomplete}}
- <div class="diff-file-box diff-box file-content gt-mt-3" id="diff-incomplete">
- <h4 class="ui top attached normal header tw-flex tw-items-center tw-justify-between">
+ <div class="diff-file-box diff-box file-content tw-mt-2" id="diff-incomplete">
+ <h4 class="ui top attached header tw-font-normal tw-flex tw-items-center tw-justify-between">
{{ctx.Locale.Tr "repo.diff.too_many_files"}}
<a class="ui basic tiny button" id="diff-show-more-files" data-href="?skip-to={{.Diff.End}}&file-only=true">{{ctx.Locale.Tr "repo.diff.show_more"}}</a>
</h4>
diff --git a/templates/repo/diff/comment_form.tmpl b/templates/repo/diff/comment_form.tmpl
index 6005ea28ef..856b3da01a 100644
--- a/templates/repo/diff/comment_form.tmpl
+++ b/templates/repo/diff/comment_form.tmpl
@@ -1,5 +1,5 @@
{{if and $.root.SignedUserID (not $.Repository.IsArchived)}}
- <form class="ui form {{if $.hidden}}gt-hidden comment-form{{end}}" action="{{$.root.Issue.Link}}/files/reviews/comments" method="post">
+ <form class="ui form {{if $.hidden}}tw-hidden comment-form{{end}}" action="{{$.root.Issue.Link}}/files/reviews/comments" method="post">
{{$.root.CsrfTokenHtml}}
<input type="hidden" name="origin" value="{{if $.root.PageIsPullFiles}}diff{{else}}timeline{{end}}">
<input type="hidden" name="latest_commit_id" value="{{$.root.AfterCommitID}}">
@@ -25,8 +25,8 @@
</div>
{{end}}
- <div class="field footer gt-mx-3">
- <span class="markup-info">{{svg "octicon-markup"}} {{ctx.Locale.Tr "repo.diff.comment.markdown_info"}}</span>
+ <div class="field footer tw-mx-2">
+ <span class="markup-info">{{svg "octicon-markdown"}} {{ctx.Locale.Tr "repo.diff.comment.markdown_info"}}</span>
<div class="tw-text-right">
{{if $.reply}}
<button class="ui submit primary tiny button btn-reply" type="submit">{{ctx.Locale.Tr "repo.diff.comment.reply"}}</button>
diff --git a/templates/repo/diff/comments.tmpl b/templates/repo/diff/comments.tmpl
index bcf91f74e8..e84b952364 100644
--- a/templates/repo/diff/comments.tmpl
+++ b/templates/repo/diff/comments.tmpl
@@ -11,7 +11,7 @@
<div class="ui top attached header comment-header tw-flex tw-items-center tw-justify-between">
<div class="comment-header-left tw-flex tw-items-center">
{{if .OriginalAuthor}}
- <span class="text black tw-font-semibold gt-mr-2">
+ <span class="text black tw-font-semibold tw-mr-1">
{{svg (MigrationIcon $.root.Repository.GetOriginalURLHostname)}}
{{.OriginalAuthor}}
</span>
@@ -60,8 +60,8 @@
<span class="no-content">{{ctx.Locale.Tr "repo.issues.no_content"}}</span>
{{end}}
</div>
- <div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div>
- <div class="edit-content-zone gt-hidden" data-update-url="{{$.root.RepoLink}}/comments/{{.ID}}" data-context="{{$.root.RepoLink}}" data-attachment-url="{{$.root.RepoLink}}/comments/{{.ID}}/attachments"></div>
+ <div id="issuecomment-{{.ID}}-raw" class="raw-content tw-hidden">{{.Content}}</div>
+ <div class="edit-content-zone tw-hidden" data-update-url="{{$.root.RepoLink}}/comments/{{.ID}}" data-context="{{$.root.RepoLink}}" data-attachment-url="{{$.root.RepoLink}}/comments/{{.ID}}/attachments"></div>
{{if .Attachments}}
{{template "repo/issue/view_content/attachments" dict "Attachments" .Attachments "RenderedContent" .RenderedContent}}
{{end}}
diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl
index cd56ebfcb2..110f8ac60b 100644
--- a/templates/repo/diff/compare.tmpl
+++ b/templates/repo/diff/compare.tmpl
@@ -28,7 +28,7 @@
{{- end -}}
{{- end -}}
<div class="ui segment choose branch">
- <a class="gt-mr-3" href="{{$.HeadRepo.Link}}/compare/{{PathEscapeSegments $.HeadBranch}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.BaseName}}/{{PathEscape $.Repository.Name}}:{{end}}{{PathEscapeSegments $.BaseBranch}}" title="{{ctx.Locale.Tr "repo.pulls.switch_head_and_base"}}">{{svg "octicon-git-compare"}}</a>
+ <a class="tw-mr-2" href="{{$.HeadRepo.Link}}/compare/{{PathEscapeSegments $.HeadBranch}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.BaseName}}/{{PathEscape $.Repository.Name}}:{{end}}{{PathEscapeSegments $.BaseBranch}}" title="{{ctx.Locale.Tr "repo.pulls.switch_head_and_base"}}">{{svg "octicon-git-compare"}}</a>
<div class="ui floating filter dropdown" data-no-results="{{ctx.Locale.Tr "repo.pulls.no_results"}}">
<div class="ui basic small button">
<span class="text">{{if $.PageIsComparePull}}{{ctx.Locale.Tr "repo.pulls.compare_base"}}{{else}}{{ctx.Locale.Tr "repo.compare.compare_base"}}{{end}}: {{$BaseCompareName}}:{{$.BaseBranch}}</span>
@@ -44,12 +44,12 @@
<div class="two column row">
<a class="reference column" href="#" data-target=".base-branch-list">
<span class="text black">
- {{svg "octicon-git-branch" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.branches"}}
+ {{svg "octicon-git-branch" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.branches"}}
</span>
</a>
<a class="reference column" href="#" data-target=".base-tag-list">
<span class="text black">
- {{svg "octicon-tag" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.tags"}}
+ {{svg "octicon-tag" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.tags"}}
</span>
</a>
</div>
@@ -75,7 +75,7 @@
{{end}}
{{end}}
</div>
- <div class="scrolling menu reference-list-menu base-tag-list gt-hidden">
+ <div class="scrolling menu reference-list-menu base-tag-list tw-hidden">
{{range .Tags}}
<div class="item {{if eq $.BaseBranch .}}selected{{end}}" data-url="{{$.RepoLink}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{end}}{{PathEscapeSegments $.HeadBranch}}">{{$BaseCompareName}}:{{.}}</div>
{{end}}
@@ -113,12 +113,12 @@
<div class="two column row">
<a class="reference column" href="#" data-target=".head-branch-list">
<span class="text black">
- {{svg "octicon-git-branch" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.branches"}}
+ {{svg "octicon-git-branch" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.branches"}}
</span>
</a>
<a class="reference column" href="#" data-target=".head-tag-list">
<span class="text black">
- {{svg "octicon-tag" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.tags"}}
+ {{svg "octicon-tag" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.tags"}}
</span>
</a>
</div>
@@ -144,7 +144,7 @@
{{end}}
{{end}}
</div>
- <div class="scrolling menu reference-list-menu head-tag-list gt-hidden">
+ <div class="scrolling menu reference-list-menu head-tag-list tw-hidden">
{{range .HeadTags}}
<div class="{{if eq $.HeadBranch .}}selected{{end}} item" data-url="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{end}}{{PathEscapeSegments .}}">{{$HeadCompareName}}:{{.}}</div>
{{end}}
@@ -171,10 +171,10 @@
{{if .IsNothingToCompare}}
{{if and $.IsSigned $.AllowEmptyPr (not .Repository.IsArchived) .PageIsComparePull}}
<div class="ui segment">{{ctx.Locale.Tr "repo.pulls.nothing_to_compare_and_allow_empty_pr"}}</div>
- <div class="ui info message show-form-container {{if .Flash}}gt-hidden{{end}}">
+ <div class="ui info message show-form-container {{if .Flash}}tw-hidden{{end}}">
<button class="ui button primary show-form">{{ctx.Locale.Tr "repo.pulls.new"}}</button>
</div>
- <div class="pullrequest-form {{if not .Flash}}gt-hidden{{end}}">
+ <div class="pullrequest-form {{if not .Flash}}tw-hidden{{end}}">
{{template "repo/issue/new_form" .}}
</div>
{{else if and .HeadIsBranch .BaseIsBranch}}
@@ -204,7 +204,7 @@
</div>
{{else}}
{{if and $.IsSigned (not .Repository.IsArchived)}}
- <div class="ui info message show-form-container {{if .Flash}}gt-hidden{{end}}">
+ <div class="ui info message show-form-container {{if .Flash}}tw-hidden{{end}}">
<button class="ui button primary show-form">{{ctx.Locale.Tr "repo.pulls.new"}}</button>
</div>
{{else if .Repository.IsArchived}}
@@ -217,7 +217,7 @@
</div>
{{end}}
{{if $.IsSigned}}
- <div class="pullrequest-form {{if not .Flash}}gt-hidden{{end}}">
+ <div class="pullrequest-form {{if not .Flash}}tw-hidden{{end}}">
{{template "repo/issue/new_form" .}}
</div>
{{end}}
diff --git a/templates/repo/diff/conversation.tmpl b/templates/repo/diff/conversation.tmpl
index 8510f94101..ef92f3bdfc 100644
--- a/templates/repo/diff/conversation.tmpl
+++ b/templates/repo/diff/conversation.tmpl
@@ -7,38 +7,38 @@
{{if $resolved}}
<div class="ui attached header resolved-placeholder tw-flex tw-items-center tw-justify-between">
<div class="ui grey text tw-flex tw-items-center tw-flex-wrap tw-gap-1">
- {{svg "octicon-check" 16 "icon gt-mr-2"}}
+ {{svg "octicon-check" 16 "icon tw-mr-1"}}
<b>{{$resolveDoer.Name}}</b> {{ctx.Locale.Tr "repo.issues.review.resolved_by"}}
{{if $invalid}}
<!--
We only handle the case $resolved=true and $invalid=true in this template because if the comment is not resolved it has the outdated label in the comments area (not the header above).
The case $resolved=false and $invalid=true is handled in repo/diff/comments.tmpl
-->
- <a href="{{AppSubUrl}}{{$referenceUrl}}" class="ui label basic small gt-ml-3" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.outdated_description"}}">
+ <a href="{{AppSubUrl}}{{$referenceUrl}}" class="ui label basic small tw-ml-2" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.outdated_description"}}">
{{ctx.Locale.Tr "repo.issues.review.outdated"}}
</a>
{{end}}
</div>
<div class="tw-flex tw-items-center tw-gap-2">
<button id="show-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="ui tiny labeled button show-outdated tw-flex tw-items-center">
- {{svg "octicon-unfold" 16 "gt-mr-3"}}
+ {{svg "octicon-unfold" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.issues.review.show_resolved"}}
</button>
- <button id="hide-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="ui tiny labeled button hide-outdated tw-flex tw-items-center gt-hidden">
- {{svg "octicon-fold" 16 "gt-mr-3"}}
+ <button id="hide-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="ui tiny labeled button hide-outdated tw-flex tw-items-center tw-hidden">
+ {{svg "octicon-fold" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.issues.review.hide_resolved"}}
</button>
</div>
</div>
{{end}}
- <div id="code-comments-{{(index .comments 0).ID}}" class="field comment-code-cloud {{if $resolved}}gt-hidden{{end}}">
+ <div id="code-comments-{{(index .comments 0).ID}}" class="field comment-code-cloud {{if $resolved}}tw-hidden{{end}}">
<div class="comment-list">
<ui class="ui comments">
{{template "repo/diff/comments" dict "root" $ "comments" .comments}}
</ui>
</div>
- <div class="tw-flex tw-justify-end tw-items-center tw-flex-wrap gt-mt-3">
- <div class="ui buttons gt-mr-2">
+ <div class="tw-flex tw-justify-end tw-items-center tw-flex-wrap tw-mt-2">
+ <div class="ui buttons tw-mr-1">
<button class="ui icon tiny basic button previous-conversation">
{{svg "octicon-arrow-up" 12 "icon"}} {{ctx.Locale.Tr "repo.issues.previous"}}
</button>
@@ -56,8 +56,8 @@
</button>
{{end}}
{{if and $.SignedUserID (not $.Repository.IsArchived)}}
- <button class="comment-form-reply ui primary tiny labeled icon button gt-ml-2 gt-mr-0">
- {{svg "octicon-reply" 16 "reply icon gt-mr-2"}}{{ctx.Locale.Tr "repo.diff.comment.reply"}}
+ <button class="comment-form-reply ui primary tiny labeled icon button tw-ml-1 tw-mr-0">
+ {{svg "octicon-reply" 16 "reply icon tw-mr-1"}}{{ctx.Locale.Tr "repo.diff.comment.reply"}}
</button>
{{end}}
</div>
diff --git a/templates/repo/diff/new_review.tmpl b/templates/repo/diff/new_review.tmpl
index 9c824db0ad..a2eae007a5 100644
--- a/templates/repo/diff/new_review.tmpl
+++ b/templates/repo/diff/new_review.tmpl
@@ -1,5 +1,5 @@
<div id="review-box">
- <button class="ui tiny primary button gt-pr-2 tw-flex js-btn-review {{if not $.IsShowingAllCommits}}disabled{{end}}" {{if not $.IsShowingAllCommits}}data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.review_only_possible_for_full_diff"}}"{{end}}>
+ <button class="ui tiny primary button tw-pr-1 tw-flex js-btn-review {{if not $.IsShowingAllCommits}}disabled{{end}}" {{if not $.IsShowingAllCommits}}data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.review_only_possible_for_full_diff"}}"{{end}}>
{{ctx.Locale.Tr "repo.diff.review"}}
<span class="ui small label review-comments-counter" data-pending-comment-number="{{.PendingCodeCommentNumber}}">{{.PendingCodeCommentNumber}}</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
diff --git a/templates/repo/diff/section_split.tmpl b/templates/repo/diff/section_split.tmpl
index 0030bbef8e..182fa737e1 100644
--- a/templates/repo/diff/section_split.tmpl
+++ b/templates/repo/diff/section_split.tmpl
@@ -44,7 +44,7 @@
{{- $rightDiff := ""}}{{if $match.RightIdx}}{{$rightDiff = $section.GetComputedInlineDiffFor $match ctx.Locale}}{{end}}
<td class="lines-num lines-num-old del-code" data-line-num="{{$line.LeftIdx}}"><span rel="diff-{{$file.NameHash}}L{{$line.LeftIdx}}"></span></td>
<td class="lines-escape del-code lines-escape-old">{{if $line.LeftIdx}}{{if $leftDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $leftDiff}}"></button>{{end}}{{end}}</td>
- <td class="lines-type-marker lines-type-marker-old del-code"><span class="gt-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
+ <td class="lines-type-marker lines-type-marker-old del-code"><span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
<td class="lines-code lines-code-old del-code">{{/*
*/}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/*
*/}}<button type="button" aria-label="{{ctx.Locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} tw-invisible{{end}}" data-side="left" data-idx="{{$line.LeftIdx}}">{{/*
@@ -59,7 +59,7 @@
*/}}</td>
<td class="lines-num lines-num-new add-code" data-line-num="{{if $match.RightIdx}}{{$match.RightIdx}}{{end}}"><span rel="{{if $match.RightIdx}}diff-{{$file.NameHash}}R{{$match.RightIdx}}{{end}}"></span></td>
<td class="lines-escape add-code lines-escape-new">{{if $match.RightIdx}}{{if $rightDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $rightDiff}}"></button>{{end}}{{end}}</td>
- <td class="lines-type-marker lines-type-marker-new add-code">{{if $match.RightIdx}}<span class="gt-mono" data-type-marker="{{$match.GetLineTypeMarker}}"></span>{{end}}</td>
+ <td class="lines-type-marker lines-type-marker-new add-code">{{if $match.RightIdx}}<span class="tw-font-mono" data-type-marker="{{$match.GetLineTypeMarker}}"></span>{{end}}</td>
<td class="lines-code lines-code-new add-code">{{/*
*/}}{{if and $.root.SignedUserID $.root.PageIsPullFiles}}{{/*
*/}}<button type="button" aria-label="{{ctx.Locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-right{{if (not $match.CanComment)}} tw-invisible{{end}}" data-side="right" data-idx="{{$match.RightIdx}}">{{/*
@@ -76,7 +76,7 @@
{{$inlineDiff := $section.GetComputedInlineDiffFor $line ctx.Locale}}
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$file.NameHash}}L{{$line.LeftIdx}}{{end}}"></span></td>
<td class="lines-escape lines-escape-old">{{if $line.LeftIdx}}{{if $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}{{end}}</td>
- <td class="lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="gt-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td>
+ <td class="lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td>
<td class="lines-code lines-code-old">{{/*
*/}}{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 2))}}{{/*
*/}}<button type="button" aria-label="{{ctx.Locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} tw-invisible{{end}}" data-side="left" data-idx="{{$line.LeftIdx}}">{{/*
@@ -91,7 +91,7 @@
*/}}</td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$file.NameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
<td class="lines-escape lines-escape-new">{{if $line.RightIdx}}{{if $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}{{end}}</td>
- <td class="lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="gt-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td>
+ <td class="lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td>
<td class="lines-code lines-code-new">{{/*
*/}}{{if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 3))}}{{/*
*/}}<button type="button" aria-label="{{ctx.Locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-right{{if (not $line.CanComment)}} tw-invisible{{end}}" data-side="right" data-idx="{{$line.RightIdx}}">{{/*
diff --git a/templates/repo/diff/section_unified.tmpl b/templates/repo/diff/section_unified.tmpl
index 112924d1a7..018c169dc3 100644
--- a/templates/repo/diff/section_unified.tmpl
+++ b/templates/repo/diff/section_unified.tmpl
@@ -44,7 +44,7 @@
<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>
{{- end -}}
</td>
- <td class="lines-type-marker"><span class="gt-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
+ <td class="lines-type-marker"><span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
{{if eq .GetType 4}}
<td class="chroma lines-code blob-hunk">{{/*
*/}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}}{{/*
diff --git a/templates/repo/diff/stats.tmpl b/templates/repo/diff/stats.tmpl
index b7acb3d49b..d0dff1bd09 100644
--- a/templates/repo/diff/stats.tmpl
+++ b/templates/repo/diff/stats.tmpl
@@ -1,5 +1,5 @@
{{Eval .file.Addition "+" .file.Deletion}}
-<span class="diff-stats-bar gt-mx-3" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.stats_desc_file" (Eval .file.Addition "+" .file.Deletion) .file.Addition .file.Deletion}}">
+<span class="diff-stats-bar tw-mx-2" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.stats_desc_file" (Eval .file.Addition "+" .file.Deletion) .file.Addition .file.Deletion}}">
{{/* if the denominator is zero, then the float result is "width: NaNpx", as before, it just works */}}
<div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .file.Addition "/" "(" .file.Addition "+" .file.Deletion "+" 0.0 ")"}}%"></div>
</span>
diff --git a/templates/repo/diff/whitespace_dropdown.tmpl b/templates/repo/diff/whitespace_dropdown.tmpl
index cfabf836d6..c54de165a4 100644
--- a/templates/repo/diff/whitespace_dropdown.tmpl
+++ b/templates/repo/diff/whitespace_dropdown.tmpl
@@ -3,25 +3,25 @@
<div class="menu">
<a class="item" href="?style={{if .IsSplitStyle}}split{{else}}unified{{end}}&whitespace=show-all&show-outdated={{$.ShowOutdatedComments}}">
<label class="tw-pointer-events-none">
- <input class="gt-mr-3 tw-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "show-all"}} checked{{end}}>
+ <input class="tw-mr-2 tw-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "show-all"}} checked{{end}}>
{{ctx.Locale.Tr "repo.diff.whitespace_show_everything"}}
</label>
</a>
<a class="item" href="?style={{if .IsSplitStyle}}split{{else}}unified{{end}}&whitespace=ignore-all&show-outdated={{$.ShowOutdatedComments}}">
<label class="tw-pointer-events-none">
- <input class="gt-mr-3 tw-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "ignore-all"}} checked{{end}}>
+ <input class="tw-mr-2 tw-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "ignore-all"}} checked{{end}}>
{{ctx.Locale.Tr "repo.diff.whitespace_ignore_all_whitespace"}}
</label>
</a>
<a class="item" href="?style={{if .IsSplitStyle}}split{{else}}unified{{end}}&whitespace=ignore-change&show-outdated={{$.ShowOutdatedComments}}">
<label class="tw-pointer-events-none">
- <input class="gt-mr-3 tw-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "ignore-change"}} checked{{end}}>
+ <input class="tw-mr-2 tw-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "ignore-change"}} checked{{end}}>
{{ctx.Locale.Tr "repo.diff.whitespace_ignore_amount_changes"}}
</label>
</a>
<a class="item" href="?style={{if .IsSplitStyle}}split{{else}}unified{{end}}&whitespace=ignore-eol&show-outdated={{$.ShowOutdatedComments}}">
<label class="tw-pointer-events-none">
- <input class="gt-mr-3 tw-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "ignore-eol"}} checked{{end}}>
+ <input class="tw-mr-2 tw-pointer-events-none" type="radio"{{if eq .WhitespaceBehavior "ignore-eol"}} checked{{end}}>
{{ctx.Locale.Tr "repo.diff.whitespace_ignore_at_eol"}}
</label>
</a>
diff --git a/templates/repo/editor/commit_form.tmpl b/templates/repo/editor/commit_form.tmpl
index 72af3e9ef3..b2fe5b1cfb 100644
--- a/templates/repo/editor/commit_form.tmpl
+++ b/templates/repo/editor/commit_form.tmpl
@@ -57,10 +57,10 @@
</label>
</div>
</div>
- <div class="quick-pull-branch-name {{if not (eq .commit_choice "commit-to-new-branch")}}gt-hidden{{end}}">
+ <div class="quick-pull-branch-name {{if not (eq .commit_choice "commit-to-new-branch")}}tw-hidden{{end}}">
<div class="new-branch-name-input field {{if .Err_NewBranchName}}error{{end}}">
{{svg "octicon-git-branch"}}
- <input type="text" name="new_branch_name" maxlength="100" value="{{.new_branch_name}}" class="input-contrast gt-mr-2 js-quick-pull-new-branch-name" placeholder="{{ctx.Locale.Tr "repo.editor.new_branch_name_desc"}}" {{if eq .commit_choice "commit-to-new-branch"}}required{{end}} title="{{ctx.Locale.Tr "repo.editor.new_branch_name"}}">
+ <input type="text" name="new_branch_name" maxlength="100" value="{{.new_branch_name}}" class="input-contrast tw-mr-1 js-quick-pull-new-branch-name" placeholder="{{ctx.Locale.Tr "repo.editor.new_branch_name_desc"}}" {{if eq .commit_choice "commit-to-new-branch"}}required{{end}} title="{{ctx.Locale.Tr "repo.editor.new_branch_name"}}">
<span class="text-muted js-quick-pull-normalization-info"></span>
</div>
</div>
diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl
index 05a8d96681..1f5652f6b5 100644
--- a/templates/repo/editor/edit.tmpl
+++ b/templates/repo/editor/edit.tmpl
@@ -34,7 +34,7 @@
{{end}}
</div>
<div class="ui bottom attached active tab segment" data-tab="write">
- <textarea id="edit_area" name="content" class="gt-hidden" data-id="repo-{{.Repository.Name}}-{{.TreePath}}"
+ <textarea id="edit_area" name="content" class="tw-hidden" data-id="repo-{{.Repository.Name}}-{{.TreePath}}"
data-url="{{.Repository.Link}}/markup"
data-context="{{.RepoLink}}"
data-previewable-extensions="{{.PreviewableExtensions}}"
diff --git a/templates/repo/editor/patch.tmpl b/templates/repo/editor/patch.tmpl
index 3de219014b..1f046a8d4e 100644
--- a/templates/repo/editor/patch.tmpl
+++ b/templates/repo/editor/patch.tmpl
@@ -20,10 +20,10 @@
</div>
<div class="field">
<div class="ui top attached tabular menu" data-write="write">
- <a class="active item" data-tab="write">{{svg "octicon-code" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.editor.new_patch"}}</a>
+ <a class="active item" data-tab="write">{{svg "octicon-code" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.editor.new_patch"}}</a>
</div>
<div class="ui bottom attached active tab segment" data-tab="write">
- <textarea id="edit_area" name="content" class="gt-hidden" data-id="repo-{{.Repository.Name}}-patch"
+ <textarea id="edit_area" name="content" class="tw-hidden" data-id="repo-{{.Repository.Name}}-patch"
data-context="{{.RepoLink}}"
data-line-wrap-extensions="{{.LineWrapExtensions}}">
{{.FileContent}}</textarea>
diff --git a/templates/repo/empty.tmpl b/templates/repo/empty.tmpl
index d3665a9f8b..cb2a5ba1e9 100644
--- a/templates/repo/empty.tmpl
+++ b/templates/repo/empty.tmpl
@@ -44,7 +44,7 @@
</div>
{{if not .Repository.IsArchived}}
- <div class="divider gt-my-0"></div>
+ <div class="divider tw-my-0"></div>
<div class="item">
<h3>{{ctx.Locale.Tr "repo.create_new_repo_command"}}</h3>
diff --git a/templates/repo/file_info.tmpl b/templates/repo/file_info.tmpl
index 33f0f87d61..86c613e3a1 100644
--- a/templates/repo/file_info.tmpl
+++ b/templates/repo/file_info.tmpl
@@ -1,4 +1,4 @@
-<div class="file-info text grey normal gt-mono">
+<div class="file-info text grey normal tw-font-mono">
{{if .FileIsSymlink}}
<div class="file-info-entry">
{{ctx.Locale.Tr "repo.symbolic_link"}}
@@ -16,7 +16,7 @@
{{end}}
{{if .LFSLock}}
<div class="file-info-entry ui" data-tooltip-content="{{.LFSLockHint}}">
- {{svg "octicon-lock" 16 "gt-mr-2"}}
+ {{svg "octicon-lock" 16 "tw-mr-1"}}
<a href="{{.LFSLockOwnerHomeLink}}">{{.LFSLockOwner}}</a>
</div>
{{end}}
diff --git a/templates/repo/find/files.tmpl b/templates/repo/find/files.tmpl
index eebdcb2b1b..548ce2f0e8 100644
--- a/templates/repo/find/files.tmpl
+++ b/templates/repo/find/files.tmpl
@@ -4,7 +4,7 @@
<div class="ui container">
<div class="tw-flex tw-items-center">
<a href="{{$.RepoLink}}">{{.RepoName}}</a>
- <span class="gt-mx-3">/</span>
+ <span class="tw-mx-2">/</span>
<div class="ui input tw-flex-1">
<input id="repo-file-find-input" type="text" autofocus data-url-data-link="{{.DataLink}}" data-url-tree-link="{{.TreeLink}}">
</div>
@@ -13,7 +13,7 @@
<tbody>
</tbody>
</table>
- <div id="repo-find-file-no-result" class="ui row center gt-mt-5 gt-hidden">
+ <div id="repo-find-file-no-result" class="ui row center tw-mt-8 tw-hidden">
<h3>{{ctx.Locale.Tr "repo.find_file.no_matching"}}</h3>
</div>
</div>
diff --git a/templates/repo/flags.tmpl b/templates/repo/flags.tmpl
index 3928cf0e17..620357bd92 100644
--- a/templates/repo/flags.tmpl
+++ b/templates/repo/flags.tmpl
@@ -11,7 +11,7 @@
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<strong>{{ctx.Locale.Tr "repo.admin.enabled_flags"}}</strong>
- <div class="ui segment gt-pl-4">
+ <div class="ui segment tw-pl-4">
{{range $flag, $checked := .Flags}}
<div class="field">
<div class="ui checkbox{{if $checked}} checked{{end}}">
diff --git a/templates/repo/forks.tmpl b/templates/repo/forks.tmpl
index 6acb89f367..412c59b60e 100644
--- a/templates/repo/forks.tmpl
+++ b/templates/repo/forks.tmpl
@@ -6,8 +6,8 @@
{{ctx.Locale.Tr "repo.forks"}}
</h2>
{{range .Forks}}
- <div class="tw-flex tw-items-center gt-py-3">
- <span class="gt-mr-2">{{ctx.AvatarUtils.Avatar .Owner}}</span>
+ <div class="tw-flex tw-items-center tw-py-2">
+ <span class="tw-mr-1">{{ctx.AvatarUtils.Avatar .Owner}}</span>
<a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a> / <a href="{{.Link}}">{{.Name}}</a>
</div>
{{end}}
diff --git a/templates/repo/graph.tmpl b/templates/repo/graph.tmpl
index 67804f117d..9eb4bd4ecb 100644
--- a/templates/repo/graph.tmpl
+++ b/templates/repo/graph.tmpl
@@ -12,7 +12,7 @@
<div class="menu">
<div class="item" data-value="...flow-hide-pr-refs">
<span class="truncate">
- {{svg "octicon-eye-closed" 16 "gt-mr-2"}}<span title="{{ctx.Locale.Tr "repo.commit_graph.hide_pr_refs"}}">{{ctx.Locale.Tr "repo.commit_graph.hide_pr_refs"}}</span>
+ {{svg "octicon-eye-closed" 16 "tw-mr-1"}}<span title="{{ctx.Locale.Tr "repo.commit_graph.hide_pr_refs"}}">{{ctx.Locale.Tr "repo.commit_graph.hide_pr_refs"}}</span>
</span>
</div>
{{range .AllRefs}}
@@ -20,37 +20,37 @@
{{if eq $refGroup "pull"}}
<div class="item" data-value="{{.Name}}">
<span class="truncate">
- {{svg "octicon-git-pull-request" 16 "gt-mr-2"}}<span title="{{.ShortName}}">#{{.ShortName}}</span>
+ {{svg "octicon-git-pull-request" 16 "tw-mr-1"}}<span title="{{.ShortName}}">#{{.ShortName}}</span>
</span>
</div>
{{else if eq $refGroup "tags"}}
<div class="item" data-value="{{.Name}}">
<span class="truncate">
- {{svg "octicon-tag" 16 "gt-mr-2"}}<span title="{{.ShortName}}">{{.ShortName}}</span>
+ {{svg "octicon-tag" 16 "tw-mr-1"}}<span title="{{.ShortName}}">{{.ShortName}}</span>
</span>
</div>
{{else if eq $refGroup "remotes"}}
<div class="item" data-value="{{.Name}}">
<span class="truncate">
- {{svg "octicon-cross-reference" 16 "gt-mr-2"}}<span title="{{.ShortName}}">{{.ShortName}}</span>
+ {{svg "octicon-cross-reference" 16 "tw-mr-1"}}<span title="{{.ShortName}}">{{.ShortName}}</span>
</span>
</div>
{{else if eq $refGroup "heads"}}
<div class="item" data-value="{{.Name}}">
<span class="truncate">
- {{svg "octicon-git-branch" 16 "gt-mr-2"}}<span title="{{.ShortName}}">{{.ShortName}}</span>
+ {{svg "octicon-git-branch" 16 "tw-mr-1"}}<span title="{{.ShortName}}">{{.ShortName}}</span>
</span>
</div>
{{end}}
{{end}}
</div>
</div>
- <button id="flow-color-monochrome" class="ui labelled icon button{{if eq .Mode "monochrome"}} active{{end}}" title="{{ctx.Locale.Tr "repo.commit_graph.monochrome"}}">{{svg "material-invert-colors" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.commit_graph.monochrome"}}</button>
- <button id="flow-color-colored" class="ui labelled icon button{{if ne .Mode "monochrome"}} active{{end}}" title="{{ctx.Locale.Tr "repo.commit_graph.color"}}">{{svg "material-palette" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.commit_graph.color"}}</button>
+ <button id="flow-color-monochrome" class="ui labelled icon button{{if eq .Mode "monochrome"}} active{{end}}" title="{{ctx.Locale.Tr "repo.commit_graph.monochrome"}}">{{svg "material-invert-colors" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.commit_graph.monochrome"}}</button>
+ <button id="flow-color-colored" class="ui labelled icon button{{if ne .Mode "monochrome"}} active{{end}}" title="{{ctx.Locale.Tr "repo.commit_graph.color"}}">{{svg "material-palette" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.commit_graph.color"}}</button>
</div>
</h2>
<div class="ui dividing"></div>
- <div class="is-loading tw-py-32 gt-hidden" id="loading-indicator"></div>
+ <div class="is-loading tw-py-32 tw-hidden" id="loading-indicator"></div>
{{template "repo/graph/svgcontainer" .}}
{{template "repo/graph/commits" .}}
</div>
diff --git a/templates/repo/graph/commits.tmpl b/templates/repo/graph/commits.tmpl
index b22527c8ef..96d09072da 100644
--- a/templates/repo/graph/commits.tmpl
+++ b/templates/repo/graph/commits.tmpl
@@ -28,10 +28,10 @@
{{- end -}}
</a>
</span>
- <span class="message tw-inline-block gt-ellipsis gt-mr-3">
+ <span class="message tw-inline-block gt-ellipsis tw-mr-2">
<span>{{RenderCommitMessage $.Context $commit.Subject ($.Repository.ComposeMetas ctx)}}</span>
</span>
- <span class="commit-refs tw-flex tw-items-center gt-mr-2">
+ <span class="commit-refs tw-flex tw-items-center tw-mr-1">
{{range $commit.Refs}}
{{$refGroup := .RefGroup}}
{{if eq $refGroup "pull"}}
@@ -58,16 +58,16 @@
{{end}}
{{end}}
</span>
- <span class="author tw-flex tw-items-center gt-mr-3">
+ <span class="author tw-flex tw-items-center tw-mr-2">
{{$userName := $commit.Commit.Author.Name}}
{{if $commit.User}}
{{if $commit.User.FullName}}
{{$userName = $commit.User.FullName}}
{{end}}
- <span class="gt-mr-2">{{ctx.AvatarUtils.Avatar $commit.User}}</span>
+ <span class="tw-mr-1">{{ctx.AvatarUtils.Avatar $commit.User}}</span>
<a href="{{$commit.User.HomeLink}}">{{$userName}}</a>
{{else}}
- <span class="gt-mr-2">{{ctx.AvatarUtils.AvatarByEmail $commit.Commit.Author.Email $userName}}</span>
+ <span class="tw-mr-1">{{ctx.AvatarUtils.AvatarByEmail $commit.Commit.Author.Email $userName}}</span>
{{$userName}}
{{end}}
</span>
diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl
index 6f2bf42297..f10efa1e3d 100644
--- a/templates/repo/header.tmpl
+++ b/templates/repo/header.tmpl
@@ -1,4 +1,4 @@
-<div class="header-wrapper">
+<div class="secondary-nav">
{{with .Repository}}
<div class="ui container">
<div class="repo-header">
@@ -81,7 +81,7 @@
{{if .IsGenerated}}<div class="fork-flag">{{ctx.Locale.Tr "repo.generated_from"}} <a href="{{(.TemplateRepo ctx).Link}}">{{(.TemplateRepo ctx).FullName}}</a></div>{{end}}
</div>
{{end}}
- <overflow-menu class="ui container secondary pointing tabular top attached borderless menu navbar tw-pt-0 tw-my-0">
+ <overflow-menu class="ui container secondary pointing tabular top attached borderless menu tw-pt-0 tw-my-0">
{{if not (or .Repository.IsBeingCreated .Repository.IsBroken)}}
<div class="overflow-menu-items">
{{if .Permission.CanRead $.UnitTypeCode}}
diff --git a/templates/repo/header_fork.tmpl b/templates/repo/header_fork.tmpl
index b186a5859f..de83000ec8 100644
--- a/templates/repo/header_fork.tmpl
+++ b/templates/repo/header_fork.tmpl
@@ -33,8 +33,8 @@
<div class="content tw-text-left">
<div class="ui list">
{{range $.UserAndOrgForks}}
- <div class="ui item gt-py-3">
- <a href="{{.Link}}">{{svg "octicon-repo-forked" 16 "gt-mr-3"}}{{.FullName}}</a>
+ <div class="ui item tw-py-2">
+ <a href="{{.Link}}">{{svg "octicon-repo-forked" 16 "tw-mr-2"}}{{.FullName}}</a>
</div>
{{end}}
</div>
diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl
index 94cff1d6c5..8a8664f147 100644
--- a/templates/repo/home.tmpl
+++ b/templates/repo/home.tmpl
@@ -19,13 +19,13 @@
</form>
</div>
<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-1" id="repo-topics">
- {{range .Topics}}<a class="ui repo-topic large label topic gt-m-0" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
+ {{range .Topics}}<a class="ui repo-topic large label topic tw-m-0" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<button id="manage_topic" class="btn interact-fg tw-text-12">{{ctx.Locale.Tr "repo.topic.manage_topics"}}</button>{{end}}
</div>
{{end}}
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
- <div class="ui form gt-hidden tw-flex tw-flex-col gt-mt-4" id="topic_edit">
- <div class="field tw-flex-1 gt-mb-2">
+ <div class="ui form tw-hidden tw-flex tw-flex-col tw-mt-4" id="topic_edit">
+ <div class="field tw-flex-1 tw-mb-1">
<div class="ui fluid multiple search selection dropdown tw-flex-wrap" data-text-count-prompt="{{ctx.Locale.Tr "repo.topic.count_prompt"}}" data-text-format-prompt="{{ctx.Locale.Tr "repo.topic.format_prompt"}}">
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}">
{{range .Topics}}
@@ -61,7 +61,7 @@
{{template "repo/sub_menu" .}}
<div class="repo-button-row">
<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-y-2">
- {{template "repo/branch_dropdown" dict "root" . "ContainerClasses" "gt-mr-2"}}
+ {{template "repo/branch_dropdown" dict "root" . "ContainerClasses" "tw-mr-1"}}
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
{{$cmpBranch := ""}}
{{if ne .Repository.ID .BaseRepo.ID}}
@@ -82,7 +82,7 @@
{{end}}
{{if and .CanWriteCode .IsViewBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}}
- <button class="ui dropdown basic compact jump button gt-mr-2"{{if not .Repository.CanEnableEditor}} disabled{{end}}>
+ <button class="ui dropdown basic compact jump button tw-mr-1"{{if not .Repository.CanEnableEditor}} disabled{{end}}>
{{ctx.Locale.Tr "repo.editor.add_file"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
@@ -107,7 +107,7 @@
</a>
{{end}}
{{if ne $n 0}}
- <span class="breadcrumb repo-path gt-ml-2">
+ <span class="breadcrumb repo-path tw-ml-1">
<a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
{{- range $i, $v := .TreeNames -}}
<span class="breadcrumb-divider">/</span>
@@ -129,12 +129,12 @@
{{svg "octicon-kebab-horizontal"}}
<div class="menu">
{{if not $.DisableDownloadSourceArchives}}
- <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "gt-mr-3"}}{{ctx.Locale.Tr "repo.download_zip"}}</a>
- <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "gt-mr-3"}}{{ctx.Locale.Tr "repo.download_tar"}}</a>
- <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.bundle" rel="nofollow">{{svg "octicon-package" 16 "gt-mr-3"}}{{ctx.Locale.Tr "repo.download_bundle"}}</a>
+ <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_zip"}}</a>
+ <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_tar"}}</a>
+ <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.bundle" rel="nofollow">{{svg "octicon-package" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_bundle"}}</a>
{{end}}
{{if .CitiationExist}}
- <a class="item" id="cite-repo-button">{{svg "octicon-cross-reference" 16 "gt-mr-3"}}{{ctx.Locale.Tr "repo.cite_this_repo"}}</a>
+ <a class="item" id="cite-repo-button">{{svg "octicon-cross-reference" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.cite_this_repo"}}</a>
{{end}}
{{range .OpenWithEditorApps}}
<a class="item js-clone-url-editor" data-href-template="{{.OpenURL}}">{{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}}</a>
@@ -147,7 +147,7 @@
{{end}}
{{if and (ne $n 0) (not .IsViewFile) (not .IsBlame)}}
<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
- {{svg "octicon-history" 16 "gt-mr-3"}}{{ctx.Locale.Tr "repo.file_history"}}
+ {{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}}
</a>
{{end}}
</div>
diff --git a/templates/repo/issue/branch_selector_field.tmpl b/templates/repo/issue/branch_selector_field.tmpl
index 3974dec2e4..dd8df57a4c 100644
--- a/templates/repo/issue/branch_selector_field.tmpl
+++ b/templates/repo/issue/branch_selector_field.tmpl
@@ -20,12 +20,12 @@
<div class="two column row">
<a class="reference column muted" href="#" data-target="#branch-list">
<span class="text black">
- {{svg "octicon-git-branch" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.branches"}}
+ {{svg "octicon-git-branch" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.branches"}}
</span>
</a>
<a class="reference column muted" href="#" data-target="#tag-list">
<span class="text">
- {{svg "octicon-tag" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.tags"}}
+ {{svg "octicon-tag" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.tags"}}
</span>
</a>
</div>
@@ -42,7 +42,7 @@
<div class="item">{{ctx.Locale.Tr "repo.pulls.no_results"}}</div>
{{end}}
</div>
- <div id="tag-list" class="scrolling menu reference-list-menu {{if not .Issue}}new-issue{{end}} gt-hidden">
+ <div id="tag-list" class="scrolling menu reference-list-menu {{if not .Issue}}new-issue{{end}} tw-hidden">
{{if .Reference}}
<div class="item text small" data-id="" data-id-selector="#ref_selector"><strong><a href="#">{{ctx.Locale.Tr "repo.clear_ref"}}</a></strong></div>
{{end}}
diff --git a/templates/repo/issue/card.tmpl b/templates/repo/issue/card.tmpl
index 3790e42b96..f3d533b862 100644
--- a/templates/repo/issue/card.tmpl
+++ b/templates/repo/issue/card.tmpl
@@ -6,7 +6,7 @@
{{end}}
</div>
{{end}}
- <div class="content gt-p-0 tw-w-full">
+ <div class="content tw-p-0 tw-w-full">
<div class="tw-flex tw-items-start">
<div class="issue-card-icon">
{{template "shared/issueicon" .}}
@@ -18,7 +18,7 @@
</a>
{{end}}
</div>
- <div class="meta gt-my-2">
+ <div class="meta tw-my-1">
<span class="text light grey muted-links">
{{if not $.Page.Repository}}{{.Repo.FullName}}{{end}}#{{.Index}}
{{$timeStr := TimeSinceUnix .GetLastEventTimestamp ctx.Locale}}
@@ -32,18 +32,18 @@
</span>
</div>
{{if .MilestoneID}}
- <div class="meta gt-my-2">
+ <div class="meta tw-my-1">
<a class="milestone" href="{{.Repo.Link}}/milestone/{{.MilestoneID}}">
- {{svg "octicon-milestone" 16 "gt-mr-2 tw-align-middle"}}
+ {{svg "octicon-milestone" 16 "tw-mr-1 tw-align-middle"}}
<span class="tw-align-middle">{{.Milestone.Name}}</span>
</a>
</div>
{{end}}
{{if $.Page.LinkedPRs}}
{{range index $.Page.LinkedPRs .ID}}
- <div class="meta gt-my-2">
+ <div class="meta tw-my-1">
<a href="{{$.Issue.Repo.Link}}/pulls/{{.Index}}">
- <span class="gt-m-0 text {{if .PullRequest.HasMerged}}purple{{else if .IsClosed}}red{{else}}green{{end}}">{{svg "octicon-git-merge" 16 "gt-mr-2 tw-align-middle"}}</span>
+ <span class="tw-m-0 text {{if .PullRequest.HasMerged}}purple{{else if .IsClosed}}red{{else}}green{{end}}">{{svg "octicon-git-merge" 16 "tw-mr-1 tw-align-middle"}}</span>
<span class="tw-align-middle">{{.Title}} <span class="text light grey">#{{.Index}}</span></span>
</a>
</div>
@@ -51,21 +51,21 @@
{{end}}
{{$tasks := .GetTasks}}
{{if gt $tasks 0}}
- <div class="meta gt-my-2">
- {{svg "octicon-checklist" 16 "gt-mr-2 tw-align-middle"}}
+ <div class="meta tw-my-1">
+ {{svg "octicon-checklist" 16 "tw-mr-1 tw-align-middle"}}
<span class="tw-align-middle">{{.GetTasksDone}} / {{$tasks}}</span>
</div>
{{end}}
</div>
{{if or .Labels .Assignees}}
- <div class="extra content labels-list gt-p-0 gt-pt-2">
+ <div class="extra content labels-list tw-p-0 tw-pt-1">
{{range .Labels}}
<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx .}}</a>
{{end}}
<div class="right floated">
{{range .Assignees}}
- <a target="_blank" href="{{.HomeLink}}" data-tooltip-content="{{ctx.Locale.Tr "repo.projects.column.assigned_to"}} {{.Name}}">{{ctx.AvatarUtils.Avatar . 28 "mini gt-mr-3"}}</a>
+ <a target="_blank" href="{{.HomeLink}}" data-tooltip-content="{{ctx.Locale.Tr "repo.projects.column.assigned_to"}} {{.Name}}">{{ctx.AvatarUtils.Avatar . 28 "mini tw-mr-2"}}</a>
{{end}}
</div>
</div>
diff --git a/templates/repo/issue/fields/checkboxes.tmpl b/templates/repo/issue/fields/checkboxes.tmpl
index b928b2be58..531f401fb7 100644
--- a/templates/repo/issue/fields/checkboxes.tmpl
+++ b/templates/repo/issue/fields/checkboxes.tmpl
@@ -1,8 +1,8 @@
-<div class="field {{if not .item.VisibleOnForm}}gt-hidden{{end}}">
+<div class="field {{if not .item.VisibleOnForm}}tw-hidden{{end}}">
{{template "repo/issue/fields/header" .}}
{{range $i, $opt := .item.Attributes.options}}
<div class="field inline">
- <div class="ui checkbox gt-mr-0 {{if and ($opt.visible) (not (SliceUtils.Contains $opt.visible "form"))}}gt-hidden{{end}}">
+ <div class="ui checkbox tw-mr-0 {{if and ($opt.visible) (not (SliceUtils.Contains $opt.visible "form"))}}tw-hidden{{end}}">
<input type="checkbox" name="form-field-{{$.item.ID}}-{{$i}}" {{if $opt.required}}required{{end}}>
<label>{{RenderMarkdownToHtml $.context $opt.label}}</label>
</div>
diff --git a/templates/repo/issue/fields/dropdown.tmpl b/templates/repo/issue/fields/dropdown.tmpl
index b8df6908e3..f4fa79738c 100644
--- a/templates/repo/issue/fields/dropdown.tmpl
+++ b/templates/repo/issue/fields/dropdown.tmpl
@@ -1,4 +1,4 @@
-<div class="field {{if not .item.VisibleOnForm}}gt-hidden{{end}}">
+<div class="field {{if not .item.VisibleOnForm}}tw-hidden{{end}}">
{{template "repo/issue/fields/header" .}}
{{/* FIXME: required validation */}}
<div class="ui fluid selection dropdown {{if .item.Attributes.multiple}}multiple clearable{{end}}">
diff --git a/templates/repo/issue/fields/input.tmpl b/templates/repo/issue/fields/input.tmpl
index ad0fe3d783..039f9a9f34 100644
--- a/templates/repo/issue/fields/input.tmpl
+++ b/templates/repo/issue/fields/input.tmpl
@@ -1,4 +1,4 @@
-<div class="field {{if not .item.VisibleOnForm}}gt-hidden{{end}}">
+<div class="field {{if not .item.VisibleOnForm}}tw-hidden{{end}}">
{{template "repo/issue/fields/header" .}}
<input type="{{if .item.Validations.is_number}}number{{else}}text{{end}}" name="form-field-{{.item.ID}}" placeholder="{{.item.Attributes.placeholder}}" value="{{.item.Attributes.value}}" {{if .item.Validations.required}}required{{end}} {{if .item.Validations.regex}}pattern="{{.item.Validations.regex}}" title="{{.item.Validations.regex}}"{{end}}>
</div>
diff --git a/templates/repo/issue/fields/markdown.tmpl b/templates/repo/issue/fields/markdown.tmpl
index 97813cc1d8..934699ed05 100644
--- a/templates/repo/issue/fields/markdown.tmpl
+++ b/templates/repo/issue/fields/markdown.tmpl
@@ -1,3 +1,3 @@
-<div class="field {{if not .item.VisibleOnForm}}gt-hidden{{end}}">
+<div class="field {{if not .item.VisibleOnForm}}tw-hidden{{end}}">
<div>{{RenderMarkdownToHtml .Context .item.Attributes.value}}</div>
</div>
diff --git a/templates/repo/issue/fields/textarea.tmpl b/templates/repo/issue/fields/textarea.tmpl
index 4f68b4038b..3ad69e1220 100644
--- a/templates/repo/issue/fields/textarea.tmpl
+++ b/templates/repo/issue/fields/textarea.tmpl
@@ -1,5 +1,5 @@
{{$useMarkdownEditor := not .item.Attributes.render}}
-<div class="field {{if not .item.VisibleOnForm}}gt-hidden{{end}} {{if $useMarkdownEditor}}combo-editor-dropzone{{end}}">
+<div class="field {{if not .item.VisibleOnForm}}tw-hidden{{end}} {{if $useMarkdownEditor}}combo-editor-dropzone{{end}}">
{{template "repo/issue/fields/header" .}}
{{/* the real form element to provide the value */}}
@@ -7,7 +7,7 @@
{{if $useMarkdownEditor}}
{{template "shared/combomarkdowneditor" (dict
- "ContainerClasses" "gt-hidden"
+ "ContainerClasses" "tw-hidden"
"MarkdownPreviewUrl" (print .root.RepoLink "/markup")
"MarkdownPreviewContext" .root.RepoLink
"TextareaContent" .item.Attributes.value
@@ -16,7 +16,7 @@
)}}
{{if .root.IsAttachmentEnabled}}
- <div class="gt-mt-4 form-field-dropzone gt-hidden">
+ <div class="tw-mt-4 form-field-dropzone tw-hidden">
{{template "repo/upload" .root}}
</div>
{{end}}
diff --git a/templates/repo/issue/filter_actions.tmpl b/templates/repo/issue/filter_actions.tmpl
index 064b75af02..e184a6b5e7 100644
--- a/templates/repo/issue/filter_actions.tmpl
+++ b/templates/repo/issue/filter_actions.tmpl
@@ -85,7 +85,7 @@
</div>
{{range .OpenProjects}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects">
- {{svg .IconName 18 "gt-mr-3"}}{{.Title}}
+ {{svg .IconName 18 "tw-mr-2"}}{{.Title}}
</div>
{{end}}
{{end}}
@@ -96,7 +96,7 @@
</div>
{{range .ClosedProjects}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects">
- {{svg .IconName 18 "gt-mr-3"}}{{.Title}}
+ {{svg .IconName 18 "tw-mr-2"}}{{.Title}}
</div>
{{end}}
{{end}}
diff --git a/templates/repo/issue/filter_list.tmpl b/templates/repo/issue/filter_list.tmpl
index cdb4b36bfd..6bf6d6b9d3 100644
--- a/templates/repo/issue/filter_list.tmpl
+++ b/templates/repo/issue/filter_list.tmpl
@@ -16,7 +16,7 @@
>
<label for="archived-filter-checkbox">
{{ctx.Locale.Tr "repo.issues.label_archived_filter"}}
- <i class="gt-ml-2" data-tooltip-content={{ctx.Locale.Tr "repo.issues.label_archive_tooltip"}}>
+ <i class="tw-ml-1" data-tooltip-content={{ctx.Locale.Tr "repo.issues.label_archive_tooltip"}}>
{{svg "octicon-info"}}
</i>
</label>
@@ -108,7 +108,7 @@
</div>
{{range .OpenProjects}}
<a rel="nofollow" class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item tw-flex" href="?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">
- {{svg .IconName 18 "gt-mr-3 tw-shrink-0"}}<span class="gt-ellipsis">{{.Title}}</span>
+ {{svg .IconName 18 "tw-mr-2 tw-shrink-0"}}<span class="gt-ellipsis">{{.Title}}</span>
</a>
{{end}}
{{end}}
@@ -119,7 +119,7 @@
</div>
{{range .ClosedProjects}}
<a rel="nofollow" class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">
- {{svg .IconName 18 "gt-mr-3"}}{{.Title}}
+ {{svg .IconName 18 "tw-mr-2"}}{{.Title}}
</a>
{{end}}
{{end}}
@@ -188,7 +188,7 @@
{{end}}
<!-- Sort -->
-<div class="list-header-sort ui small dropdown downward type jump item">
+<div class="list-header-sort ui dropdown downward type jump item">
<span class="text">
{{ctx.Locale.Tr "repo.issues.filter_sort"}}
</span>
diff --git a/templates/repo/issue/filters.tmpl b/templates/repo/issue/filters.tmpl
index 56c65e2401..06e7c1aa6c 100644
--- a/templates/repo/issue/filters.tmpl
+++ b/templates/repo/issue/filters.tmpl
@@ -1,7 +1,7 @@
<div id="issue-filters" class="issue-list-toolbar">
<div class="issue-list-toolbar-left">
{{if and $.CanWriteIssuesOrPulls .Issues}}
- <input type="checkbox" autocomplete="off" class="issue-checkbox-all gt-mr-4" title="{{ctx.Locale.Tr "repo.issues.action_check_all"}}">
+ <input type="checkbox" autocomplete="off" class="issue-checkbox-all tw-mr-4" title="{{ctx.Locale.Tr "repo.issues.action_check_all"}}">
{{end}}
{{template "repo/issue/openclose" .}}
<!-- Total Tracked Time -->
diff --git a/templates/repo/issue/labels.tmpl b/templates/repo/issue/labels.tmpl
index 86e4bae0f7..6dc7e4ef64 100644
--- a/templates/repo/issue/labels.tmpl
+++ b/templates/repo/issue/labels.tmpl
@@ -2,7 +2,7 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository labels">
{{template "repo/header" .}}
<div class="ui container">
- <div class="navbar gt-mb-4">
+ <div class="navbar tw-mb-4">
{{template "repo/issue/navbar" .}}
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
diff --git a/templates/repo/issue/labels/edit_delete_label.tmpl b/templates/repo/issue/labels/edit_delete_label.tmpl
index 7ddc38a387..98e0f47020 100644
--- a/templates/repo/issue/labels/edit_delete_label.tmpl
+++ b/templates/repo/issue/labels/edit_delete_label.tmpl
@@ -30,7 +30,7 @@
</div>
<br>
<small class="desc">{{ctx.Locale.Tr "repo.issues.label_exclusive_desc"}}</small>
- <div class="desc gt-ml-2 gt-mt-3 gt-hidden label-exclusive-warning">
+ <div class="desc tw-ml-1 tw-mt-2 tw-hidden label-exclusive-warning">
{{svg "octicon-alert"}} {{ctx.Locale.Tr "repo.issues.label_exclusive_warning"}}
</div>
<br>
@@ -40,7 +40,7 @@
<input class="label-is-archived-input" name="is_archived" type="checkbox">
<label>{{ctx.Locale.Tr "repo.issues.label_archive"}}</label>
</div>
- <i class="gt-ml-2" data-tooltip-content={{ctx.Locale.Tr "repo.issues.label_archive_tooltip"}}>
+ <i class="tw-ml-1" data-tooltip-content={{ctx.Locale.Tr "repo.issues.label_archive_tooltip"}}>
{{svg "octicon-info"}}
</i>
</div>
diff --git a/templates/repo/issue/labels/label.tmpl b/templates/repo/issue/labels/label.tmpl
index 3ecae09373..2480115129 100644
--- a/templates/repo/issue/labels/label.tmpl
+++ b/templates/repo/issue/labels/label.tmpl
@@ -1,5 +1,5 @@
<a
- class="item {{if not .label.IsChecked}}gt-hidden{{end}}"
+ class="item {{if not .label.IsChecked}}tw-hidden{{end}}"
id="label_{{.label.ID}}"
href="{{.root.RepoLink}}/{{if or .root.IsPull .root.Issue.IsPull}}pulls{{else}}issues{{end}}?labels={{.label.ID}}"{{/* FIXME: use .root.Issue.Link or create .root.Link */}}
>
diff --git a/templates/repo/issue/labels/label_list.tmpl b/templates/repo/issue/labels/label_list.tmpl
index cc244af17e..ad4d8697e7 100644
--- a/templates/repo/issue/labels/label_list.tmpl
+++ b/templates/repo/issue/labels/label_list.tmpl
@@ -3,7 +3,7 @@
<div class="ui right">
<div class="ui secondary menu">
<!-- Sort -->
- <div class="item ui jump dropdown gt-py-3">
+ <div class="item ui jump dropdown tw-py-2">
<span class="text">
{{ctx.Locale.Tr "repo.issues.filter_sort"}}
</span>
diff --git a/templates/repo/issue/labels/labels_selector_field.tmpl b/templates/repo/issue/labels/labels_selector_field.tmpl
index e42a1de895..067361bf1e 100644
--- a/templates/repo/issue/labels/labels_selector_field.tmpl
+++ b/templates/repo/issue/labels/labels_selector_field.tmpl
@@ -2,7 +2,7 @@
<span class="text muted flex-text-block">
<strong>{{ctx.Locale.Tr "repo.issues.new.labels"}}</strong>
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
- {{svg "octicon-gear" 16 "gt-ml-2"}}
+ {{svg "octicon-gear" 16 "tw-ml-1"}}
{{end}}
</span>
<div class="filter menu" {{if .Issue}}data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/labels"{{else}}data-id="#label_ids"{{end}}>
diff --git a/templates/repo/issue/labels/labels_sidebar.tmpl b/templates/repo/issue/labels/labels_sidebar.tmpl
index 4f41054a91..be30baba92 100644
--- a/templates/repo/issue/labels/labels_sidebar.tmpl
+++ b/templates/repo/issue/labels/labels_sidebar.tmpl
@@ -1,5 +1,5 @@
<div class="ui labels list">
- <span class="no-select item {{if .root.HasSelectedLabel}}gt-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_label"}}</span>
+ <span class="no-select item {{if .root.HasSelectedLabel}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_label"}}</span>
<span class="labels-list">
{{range .root.Labels}}
{{template "repo/issue/labels/label" dict "root" $.root "label" .}}
diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl
index 45bddefa42..1654cd2c90 100644
--- a/templates/repo/issue/list.tmpl
+++ b/templates/repo/issue/list.tmpl
@@ -14,7 +14,7 @@
</div>
{{end}}
- <div class="list-header">
+ <div class="list-header list-header-issues">
{{template "repo/issue/navbar" .}}
{{template "repo/issue/search" .}}
{{if not .Repository.IsArchived}}
@@ -32,7 +32,7 @@
{{template "repo/issue/filters" .}}
- <div id="issue-actions" class="issue-list-toolbar gt-hidden">
+ <div id="issue-actions" class="issue-list-toolbar tw-hidden">
<div class="issue-list-toolbar-left">
{{template "repo/issue/openclose" .}}
<!-- Total Tracked Time -->
diff --git a/templates/repo/issue/milestone/filter_list.tmpl b/templates/repo/issue/milestone/filter_list.tmpl
index 45f9866a16..ecfb95bb13 100644
--- a/templates/repo/issue/milestone/filter_list.tmpl
+++ b/templates/repo/issue/milestone/filter_list.tmpl
@@ -1,5 +1,5 @@
<!-- Sort -->
-<div class="list-header-sort ui small dropdown type jump item">
+<div class="list-header-sort ui dropdown type jump item">
<span class="text">
{{ctx.Locale.Tr "repo.issues.filter_sort"}}
</span>
diff --git a/templates/repo/issue/milestone/select_menu.tmpl b/templates/repo/issue/milestone/select_menu.tmpl
index 6f8c6c85c2..9b0492ce52 100644
--- a/templates/repo/issue/milestone/select_menu.tmpl
+++ b/templates/repo/issue/milestone/select_menu.tmpl
@@ -18,7 +18,7 @@
</div>
{{range .OpenMilestones}}
<a class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}">
- {{svg "octicon-milestone" 16 "gt-mr-2"}}
+ {{svg "octicon-milestone" 16 "tw-mr-1"}}
{{.Name}}
</a>
{{end}}
@@ -30,7 +30,7 @@
</div>
{{range .ClosedMilestones}}
<a class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}">
- {{svg "octicon-milestone" 16 "gt-mr-2"}}
+ {{svg "octicon-milestone" 16 "tw-mr-1"}}
{{.Name}}
</a>
{{end}}
diff --git a/templates/repo/issue/milestone_issues.tmpl b/templates/repo/issue/milestone_issues.tmpl
index 8ba7eecf4d..5bae6fc6d5 100644
--- a/templates/repo/issue/milestone_issues.tmpl
+++ b/templates/repo/issue/milestone_issues.tmpl
@@ -4,7 +4,7 @@
<div class="ui container">
{{template "base/alert" .}}
<div class="tw-flex">
- <h1 class="gt-mb-3">{{.Milestone.Name}}</h1>
+ <h1 class="tw-mb-2">{{.Milestone.Name}}</h1>
{{if not .Repository.IsArchived}}
<div class="text right tw-flex-1">
{{if or .CanWriteIssues .CanWritePulls}}
@@ -22,7 +22,7 @@
{{end}}
</div>
{{if .Milestone.RenderedContent}}
- <div class="markup content gt-mb-4">
+ <div class="markup content tw-mb-4">
{{.Milestone.RenderedContent}}
</div>
{{end}}
@@ -46,7 +46,7 @@
{{end}}
{{end}}
</div>
- <div class="gt-mr-3">{{ctx.Locale.Tr "repo.milestones.completeness" .Milestone.Completeness}}</div>
+ <div class="tw-mr-2">{{ctx.Locale.Tr "repo.milestones.completeness" .Milestone.Completeness}}</div>
{{if .TotalTrackedTime}}
<div data-tooltip-content='{{ctx.Locale.Tr "tracked_time_summary"}}'>
{{svg "octicon-clock"}}
diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl
index 57b697d8fd..bce7ad8717 100644
--- a/templates/repo/issue/milestones.tmpl
+++ b/templates/repo/issue/milestones.tmpl
@@ -19,12 +19,12 @@
{{range .Milestones}}
<li class="milestone-card">
<div class="milestone-header">
- <h3 class="flex-text-block gt-m-0">
+ <h3 class="flex-text-block tw-m-0">
{{svg "octicon-milestone" 16}}
<a class="muted" href="{{$.RepoLink}}/milestone/{{.ID}}">{{.Name}}</a>
</h3>
<div class="tw-flex tw-items-center">
- <span class="gt-mr-3">{{.Completeness}}%</span>
+ <span class="tw-mr-2">{{.Completeness}}%</span>
<progress value="{{.Completeness}}" max="100"></progress>
</div>
</div>
diff --git a/templates/repo/issue/navbar.tmpl b/templates/repo/issue/navbar.tmpl
index 16110597ed..30e42c77cc 100644
--- a/templates/repo/issue/navbar.tmpl
+++ b/templates/repo/issue/navbar.tmpl
@@ -1,4 +1,4 @@
-<h2 class="ui compact small menu header small-menu-items issue-list-navbar">
+<h2 class="ui compact small menu small-menu-items issue-list-navbar">
<a class="{{if .PageIsLabels}}active {{end}}item" href="{{.RepoLink}}/labels">{{ctx.Locale.Tr "repo.labels"}}</a>
<a class="{{if .PageIsMilestones}}active {{end}}item" href="{{.RepoLink}}/milestones">{{ctx.Locale.Tr "repo.milestones"}}</a>
</h2>
diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl
index ba1e19bf07..88a6c39e52 100644
--- a/templates/repo/issue/new_form.tmpl
+++ b/templates/repo/issue/new_form.tmpl
@@ -7,9 +7,9 @@
<div class="ui comments">
<div class="comment">
{{ctx.AvatarUtils.Avatar .SignedUser 40}}
- <div class="ui segment content gt-my-0">
+ <div class="ui segment content tw-my-0">
<div class="field">
- <input name="title" id="issue_title" placeholder="{{ctx.Locale.Tr "repo.milestones.title"}}" value="{{if .TitleQuery}}{{.TitleQuery}}{{else if .IssueTemplateTitle}}{{.IssueTemplateTitle}}{{else}}{{.title}}{{end}}" autofocus required maxlength="255" autocomplete="off">
+ <input name="title" class="js-autofocus-end" id="issue_title" placeholder="{{ctx.Locale.Tr "repo.milestones.title"}}" value="{{if .TitleQuery}}{{.TitleQuery}}{{else if .IssueTemplateTitle}}{{.IssueTemplateTitle}}{{else}}{{.title}}{{end}}" required maxlength="255" autocomplete="off">
{{if .PageIsComparePull}}
<div class="title_wip_desc" data-wip-prefixes="{{JsonUtils.EncodeToString .PullRequestWorkInProgressPrefixes}}">{{ctx.Locale.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0)}}</div>
{{end}}
@@ -60,7 +60,7 @@
<span class="text flex-text-block">
<strong>{{ctx.Locale.Tr "repo.issues.new.milestone"}}</strong>
{{if .HasIssuesOrPullsWritePermission}}
- {{svg "octicon-gear" 16 "gt-ml-2"}}
+ {{svg "octicon-gear" 16 "tw-ml-1"}}
{{end}}
</span>
<div class="menu">
@@ -68,11 +68,11 @@
</div>
</div>
<div class="ui select-milestone list">
- <span class="no-select item {{if .Milestone}}gt-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_milestone"}}</span>
+ <span class="no-select item {{if .Milestone}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_milestone"}}</span>
<div class="selected">
{{if .Milestone}}
<a class="item muted sidebar-item-link" href="{{.RepoLink}}/issues?milestone={{.Milestone.ID}}">
- {{svg "octicon-milestone" 18 "gt-mr-3"}}
+ {{svg "octicon-milestone" 18 "tw-mr-2"}}
{{.Milestone.Name}}
</a>
{{end}}
@@ -87,7 +87,7 @@
<span class="text flex-text-block">
<strong>{{ctx.Locale.Tr "repo.issues.new.projects"}}</strong>
{{if .HasIssuesOrPullsWritePermission}}
- {{svg "octicon-gear" 16 "gt-ml-2"}}
+ {{svg "octicon-gear" 16 "tw-ml-1"}}
{{end}}
</span>
<div class="menu">
@@ -110,7 +110,7 @@
</div>
{{range .OpenProjects}}
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
- {{svg .IconName 18 "gt-mr-3"}}{{.Title}}
+ {{svg .IconName 18 "tw-mr-2"}}{{.Title}}
</a>
{{end}}
{{end}}
@@ -121,7 +121,7 @@
</div>
{{range .ClosedProjects}}
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
- {{svg .IconName 18 "gt-mr-3"}}{{.Title}}
+ {{svg .IconName 18 "tw-mr-2"}}{{.Title}}
</a>
{{end}}
{{end}}
@@ -129,11 +129,11 @@
</div>
</div>
<div class="ui select-project list">
- <span class="no-select item {{if .Project}}gt-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_projects"}}</span>
+ <span class="no-select item {{if .Project}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_projects"}}</span>
<div class="selected">
{{if .Project}}
<a class="item muted sidebar-item-link" href="{{.Project.Link ctx}}">
- {{svg .Project.IconName 18 "gt-mr-3"}}{{.Project.Title}}
+ {{svg .Project.IconName 18 "tw-mr-2"}}{{.Project.Title}}
</a>
{{end}}
</div>
@@ -145,7 +145,7 @@
<span class="text flex-text-block">
<strong>{{ctx.Locale.Tr "repo.issues.new.assignees"}}</strong>
{{if .HasIssuesOrPullsWritePermission}}
- {{svg "octicon-gear" 16 "gt-ml-2"}}
+ {{svg "octicon-gear" 16 "tw-ml-1"}}
{{end}}
</span>
<div class="filter menu" data-id="#assignee_ids">
@@ -158,20 +158,20 @@
<a class="item muted" href="#" data-id="{{.ID}}" data-id-selector="#assignee_{{.ID}}">
<span class="octicon-check tw-invisible">{{svg "octicon-check"}}</span>
<span class="text">
- {{ctx.AvatarUtils.Avatar . 28 "gt-mr-3"}}{{template "repo/search_name" .}}
+ {{ctx.AvatarUtils.Avatar . 28 "tw-mr-2"}}{{template "repo/search_name" .}}
</span>
</a>
{{end}}
</div>
</div>
<div class="ui assignees list">
- <span class="no-select item {{if .HasSelectedLabel}}gt-hidden{{end}}">
+ <span class="no-select item {{if .HasSelectedLabel}}tw-hidden{{end}}">
{{ctx.Locale.Tr "repo.issues.new.no_assignees"}}
</span>
<div class="selected">
{{range .Assignees}}
- <a class="item gt-p-2 muted gt-hidden" id="assignee_{{.ID}}" href="{{$.RepoLink}}/issues?assignee={{.ID}}">
- {{ctx.AvatarUtils.Avatar . 28 "gt-mr-3 tw-align-middle"}}{{.GetDisplayName}}
+ <a class="item tw-p-1 muted tw-hidden" id="assignee_{{.ID}}" href="{{$.RepoLink}}/issues?assignee={{.ID}}">
+ {{ctx.AvatarUtils.Avatar . 28 "tw-mr-2 tw-align-middle"}}{{.GetDisplayName}}
</a>
{{end}}
</div>
diff --git a/templates/repo/issue/openclose.tmpl b/templates/repo/issue/openclose.tmpl
index 38848c51ac..eb2d6e09ee 100644
--- a/templates/repo/issue/openclose.tmpl
+++ b/templates/repo/issue/openclose.tmpl
@@ -1,16 +1,16 @@
<div class="small-menu-items ui compact tiny menu">
<a class="{{if eq .State "open"}}active {{end}}item" href="{{if eq .State "open"}}{{.AllStatesLink}}{{else}}{{.OpenLink}}{{end}}">
{{if .PageIsMilestones}}
- {{svg "octicon-milestone" 16 "gt-mr-3"}}
+ {{svg "octicon-milestone" 16 "tw-mr-2"}}
{{else if .PageIsPullList}}
- {{svg "octicon-git-pull-request" 16 "gt-mr-3"}}
+ {{svg "octicon-git-pull-request" 16 "tw-mr-2"}}
{{else}}
- {{svg "octicon-issue-opened" 16 "gt-mr-3"}}
+ {{svg "octicon-issue-opened" 16 "tw-mr-2"}}
{{end}}
{{ctx.Locale.PrettyNumber .OpenCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.open_title"}}
</a>
<a class="{{if eq .State "closed"}}active {{end}}item" href="{{if eq .State "closed"}}{{.AllStatesLink}}{{else}}{{.ClosedLink}}{{end}}">
- {{svg "octicon-check" 16 "gt-mr-3"}}
+ {{svg "octicon-check" 16 "tw-mr-2"}}
{{ctx.Locale.PrettyNumber .ClosedCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.closed_title"}}
</a>
</div>
diff --git a/templates/repo/issue/search.tmpl b/templates/repo/issue/search.tmpl
index 4727b26154..769387b51c 100644
--- a/templates/repo/issue/search.tmpl
+++ b/templates/repo/issue/search.tmpl
@@ -11,7 +11,7 @@
{{end}}
{{template "shared/search/input" dict "Value" .Keyword}}
{{if .PageIsIssueList}}
- <button id="issue-list-quick-goto" class="ui small icon button gt-hidden" data-tooltip-content="{{ctx.Locale.Tr "explore.go_to"}}" data-repo-link="{{.RepoLink}}">{{svg "octicon-hash"}}</button>
+ <button id="issue-list-quick-goto" class="ui small icon button tw-hidden" data-tooltip-content="{{ctx.Locale.Tr "explore.go_to"}}" data-repo-link="{{.RepoLink}}">{{svg "octicon-hash"}}</button>
{{end}}
{{template "shared/search/button"}}
</div>
diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl
index c885f2e93a..bb8863a170 100644
--- a/templates/repo/issue/view_content.tmpl
+++ b/templates/repo/issue/view_content.tmpl
@@ -59,8 +59,8 @@
<span class="no-content">{{ctx.Locale.Tr "repo.issues.no_content"}}</span>
{{end}}
</div>
- <div id="issue-{{.Issue.ID}}-raw" class="raw-content gt-hidden">{{.Issue.Content}}</div>
- <div class="edit-content-zone gt-hidden" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/content" data-context="{{.RepoLink}}" data-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/attachments" data-view-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/view-attachments"></div>
+ <div id="issue-{{.Issue.ID}}-raw" class="raw-content tw-hidden">{{.Issue.Content}}</div>
+ <div class="edit-content-zone tw-hidden" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/content" data-context="{{.RepoLink}}" data-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/attachments" data-view-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/view-attachments"></div>
{{if .Issue.Attachments}}
{{template "repo/issue/view_content/attachments" dict "Attachments" .Issue.Attachments "RenderedContent" .Issue.RenderedContent}}
{{end}}
@@ -171,7 +171,7 @@
{{template "repo/issue/view_content/reference_issue_dialog" .}}
-<div class="gt-hidden" id="no-content">
+<div class="tw-hidden" id="no-content">
<span class="no-content">{{ctx.Locale.Tr "repo.issues.no_content"}}</span>
</div>
diff --git a/templates/repo/issue/view_content/attachments.tmpl b/templates/repo/issue/view_content/attachments.tmpl
index 0635a201be..2155f78656 100644
--- a/templates/repo/issue/view_content/attachments.tmpl
+++ b/templates/repo/issue/view_content/attachments.tmpl
@@ -5,7 +5,7 @@
{{$hasThumbnails := false}}
{{- range .Attachments -}}
<div class="tw-flex">
- <div class="tw-flex-1 gt-p-3">
+ <div class="tw-flex-1 tw-p-2">
<a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}" title="{{ctx.Locale.Tr "repo.issues.attachment.open_tab" .Name}}">
{{if FilenameIsImage .Name}}
{{if not (StringUtils.Contains (StringUtils.ToString $.RenderedContent) .UUID)}}
@@ -18,7 +18,7 @@
<span><strong>{{.Name}}</strong></span>
</a>
</div>
- <div class="gt-p-3 tw-flex tw-items-center">
+ <div class="tw-p-2 tw-flex tw-items-center">
<span class="ui text grey">{{.Size | FileSize}}</span>
</div>
</div>
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index 2038eb7c48..162762950d 100644
--- a/templates/repo/issue/view_content/comments.tmpl
+++ b/templates/repo/issue/view_content/comments.tmpl
@@ -28,7 +28,7 @@
<div class="ui top attached header comment-header tw-flex tw-items-center tw-justify-between" role="heading" aria-level="3">
<div class="comment-header-left tw-flex tw-items-center">
{{if .OriginalAuthor}}
- <span class="text black tw-font-semibold gt-mr-2">
+ <span class="text black tw-font-semibold tw-mr-1">
{{svg (MigrationIcon $.Repository.GetOriginalURLHostname)}}
{{.OriginalAuthor}}
</span>
@@ -66,8 +66,8 @@
<span class="no-content">{{ctx.Locale.Tr "repo.issues.no_content"}}</span>
{{end}}
</div>
- <div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div>
- <div class="edit-content-zone gt-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div>
+ <div id="issuecomment-{{.ID}}-raw" class="raw-content tw-hidden">{{.Content}}</div>
+ <div class="edit-content-zone tw-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div>
{{if .Attachments}}
{{template "repo/issue/view_content/attachments" dict "Attachments" .Attachments "RenderedContent" .RenderedContent}}
{{end}}
@@ -440,8 +440,8 @@
<span class="no-content">{{ctx.Locale.Tr "repo.issues.no_content"}}</span>
{{end}}
</div>
- <div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div>
- <div class="edit-content-zone gt-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div>
+ <div id="issuecomment-{{.ID}}-raw" class="raw-content tw-hidden">{{.Content}}</div>
+ <div class="edit-content-zone tw-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div>
{{if .Attachments}}
{{template "repo/issue/view_content/attachments" dict "Attachments" .Attachments "RenderedContent" .RenderedContent}}
{{end}}
diff --git a/templates/repo/issue/view_content/conversation.tmpl b/templates/repo/issue/view_content/conversation.tmpl
index f94aa290fd..a28956937b 100644
--- a/templates/repo/issue/view_content/conversation.tmpl
+++ b/templates/repo/issue/view_content/conversation.tmpl
@@ -3,27 +3,27 @@
{{$resolveDoer := (index .comments 0).ResolveDoer}}
{{$isNotPending := (not (eq (index .comments 0).Review.Type 0))}}
<div class="ui segments conversation-holder">
- <div class="ui segment collapsible-comment-box gt-py-3 tw-flex tw-items-center tw-justify-between">
+ <div class="ui segment collapsible-comment-box tw-py-2 tw-flex tw-items-center tw-justify-between">
<div class="tw-flex tw-items-center">
- <a href="{{(index .comments 0).CodeCommentLink ctx}}" class="file-comment gt-ml-3 gt-word-break">{{(index .comments 0).TreePath}}</a>
+ <a href="{{(index .comments 0).CodeCommentLink ctx}}" class="file-comment tw-ml-2 gt-word-break">{{(index .comments 0).TreePath}}</a>
{{if $invalid}}
- <span class="ui label basic small gt-ml-3" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.outdated_description"}}">
+ <span class="ui label basic small tw-ml-2" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.outdated_description"}}">
{{ctx.Locale.Tr "repo.issues.review.outdated"}}
</span>
{{end}}
</div>
<div>
{{if or $invalid $resolved}}
- <button id="show-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="{{if not $resolved}}gt-hidden {{end}}ui compact labeled button show-outdated tw-flex tw-items-center">
- {{svg "octicon-unfold" 16 "gt-mr-3"}}
+ <button id="show-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="{{if not $resolved}}tw-hidden {{end}}ui compact labeled button show-outdated tw-flex tw-items-center">
+ {{svg "octicon-unfold" 16 "tw-mr-2"}}
{{if $resolved}}
{{ctx.Locale.Tr "repo.issues.review.show_resolved"}}
{{else}}
{{ctx.Locale.Tr "repo.issues.review.show_outdated"}}
{{end}}
</button>
- <button id="hide-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="{{if $resolved}}gt-hidden {{end}}ui compact labeled button hide-outdated tw-flex tw-items-center">
- {{svg "octicon-fold" 16 "gt-mr-3"}}
+ <button id="hide-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="{{if $resolved}}tw-hidden {{end}}ui compact labeled button hide-outdated tw-flex tw-items-center">
+ {{svg "octicon-fold" 16 "tw-mr-2"}}
{{if $resolved}}
{{ctx.Locale.Tr "repo.issues.review.hide_resolved"}}
{{else}}
@@ -36,7 +36,7 @@
{{$diff := (CommentMustAsDiff ctx (index .comments 0))}}
{{if $diff}}
{{$file := (index $diff.Files 0)}}
- <div id="code-preview-{{(index .comments 0).ID}}" class="ui table segment{{if $resolved}} gt-hidden{{end}}">
+ <div id="code-preview-{{(index .comments 0).ID}}" class="ui table segment{{if $resolved}} tw-hidden{{end}}">
<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}}">
<div class="file-body file-code code-view code-diff code-diff-unified unicode-escaped">
<table>
@@ -48,11 +48,11 @@
</div>
</div>
{{end}}
- <div id="code-comments-{{(index .comments 0).ID}}" class="comment-code-cloud ui segment{{if $resolved}} gt-hidden{{end}}">
- <div class="ui comments gt-mb-0">
+ <div id="code-comments-{{(index .comments 0).ID}}" class="comment-code-cloud ui segment{{if $resolved}} tw-hidden{{end}}">
+ <div class="ui comments tw-mb-0">
{{range .comments}}
{{$createdSubStr:= TimeSinceUnix .CreatedUnix ctx.Locale}}
- <div class="comment code-comment gt-pb-4" id="{{.HashTag}}">
+ <div class="comment code-comment tw-pb-4" id="{{.HashTag}}">
<div class="content">
<div class="header comment-header">
<div class="comment-header-left tw-flex tw-items-center">
@@ -92,8 +92,8 @@
<span class="no-content">{{ctx.Locale.Tr "repo.issues.no_content"}}</span>
{{end}}
</div>
- <div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div>
- <div class="edit-content-zone gt-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div>
+ <div id="issuecomment-{{.ID}}-raw" class="raw-content tw-hidden">{{.Content}}</div>
+ <div class="edit-content-zone tw-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div>
{{if .Attachments}}
{{template "repo/issue/view_content/attachments" dict "Attachments" .Attachments "RenderedContent" .RenderedContent}}
{{end}}
@@ -106,11 +106,11 @@
</div>
{{end}}
</div>
- <div class="code-comment-buttons tw-flex tw-items-center tw-flex-wrap gt-mt-3 gt-mb-2 gt-mx-3">
+ <div class="code-comment-buttons tw-flex tw-items-center tw-flex-wrap tw-mt-2 tw-mb-1 tw-mx-2">
<div class="tw-flex-1">
{{if $resolved}}
<div class="ui grey text">
- {{svg "octicon-check" 16 "gt-mr-2"}}
+ {{svg "octicon-check" 16 "tw-mr-1"}}
<b>{{$resolveDoer.Name}}</b> {{ctx.Locale.Tr "repo.issues.review.resolved_by"}}
</div>
{{end}}
@@ -126,8 +126,8 @@
</button>
{{end}}
{{if and $.SignedUserID (not $.Repository.IsArchived)}}
- <button class="comment-form-reply ui primary tiny labeled icon button gt-ml-2 gt-mr-0">
- {{svg "octicon-reply" 16 "reply icon gt-mr-2"}}{{ctx.Locale.Tr "repo.diff.comment.reply"}}
+ <button class="comment-form-reply ui primary tiny labeled icon button tw-ml-1 tw-mr-0">
+ {{svg "octicon-reply" 16 "reply icon tw-mr-1"}}{{ctx.Locale.Tr "repo.diff.comment.reply"}}
</button>
{{end}}
</div>
diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl
index 08f666d210..2d657c74ac 100644
--- a/templates/repo/issue/view_content/pull.tmpl
+++ b/templates/repo/issue/view_content/pull.tmpl
@@ -35,7 +35,7 @@
{{if .IsPullBranchDeletable}}
<div class="item item-section text tw-flex-1">
<div class="item-section-left">
- <h3 class="gt-mb-3">
+ <h3 class="tw-mb-2">
{{ctx.Locale.Tr "repo.pulls.merged_success"}}
</h3>
<div class="merge-section-info">
@@ -50,7 +50,7 @@
{{else if .Issue.IsClosed}}
<div class="item item-section text tw-flex-1">
<div class="item-section-left">
- <h3 class="gt-mb-3">{{ctx.Locale.Tr "repo.pulls.closed"}}</h3>
+ <h3 class="tw-mb-2">{{ctx.Locale.Tr "repo.pulls.closed"}}</h3>
<div class="merge-section-info">
{{if .IsPullRequestBroken}}
{{ctx.Locale.Tr "repo.pulls.cant_reopen_deleted_branch"}}
diff --git a/templates/repo/issue/view_content/pull_merge_instruction.tmpl b/templates/repo/issue/view_content/pull_merge_instruction.tmpl
index 12b0c4b4e0..d585d36574 100644
--- a/templates/repo/issue/view_content/pull_merge_instruction.tmpl
+++ b/templates/repo/issue/view_content/pull_merge_instruction.tmpl
@@ -1,6 +1,6 @@
<div class="divider"></div>
<div class="instruct-toggle"> {{ctx.Locale.Tr "repo.pulls.cmd_instruction_hint"}} </div>
-<div class="instruct-content gt-mt-3 gt-hidden">
+<div class="instruct-content tw-mt-2 tw-hidden">
<div><h3>{{ctx.Locale.Tr "repo.pulls.cmd_instruction_checkout_title"}}</h3>{{ctx.Locale.Tr "repo.pulls.cmd_instruction_checkout_desc"}}</div>
{{$localBranch := .PullRequest.HeadBranch}}
{{if ne .PullRequest.HeadRepo.ID .PullRequest.BaseRepo.ID}}
@@ -21,25 +21,25 @@
<div>git checkout {{.PullRequest.BaseBranch}}</div>
<div>git merge --no-ff {{$localBranch}}</div>
</div>
- <div class="gt-hidden" data-pull-merge-style="rebase">
+ <div class="tw-hidden" data-pull-merge-style="rebase">
<div>git checkout {{.PullRequest.BaseBranch}}</div>
<div>git merge --ff-only {{$localBranch}}</div>
</div>
- <div class="gt-hidden" data-pull-merge-style="rebase-merge">
+ <div class="tw-hidden" data-pull-merge-style="rebase-merge">
<div>git checkout {{$localBranch}}</div>
<div>git rebase {{.PullRequest.BaseBranch}}</div>
<div>git checkout {{.PullRequest.BaseBranch}}</div>
<div>git merge --no-ff {{$localBranch}}</div>
</div>
- <div class="gt-hidden" data-pull-merge-style="squash">
+ <div class="tw-hidden" data-pull-merge-style="squash">
<div>git checkout {{.PullRequest.BaseBranch}}</div>
<div>git merge --squash {{$localBranch}}</div>
</div>
- <div class="gt-hidden" data-pull-merge-style="fast-forward-only">
+ <div class="tw-hidden" data-pull-merge-style="fast-forward-only">
<div>git checkout {{.PullRequest.BaseBranch}}</div>
<div>git merge --ff-only {{$localBranch}}</div>
</div>
- <div class="gt-hidden" data-pull-merge-style="manually-merged">
+ <div class="tw-hidden" data-pull-merge-style="manually-merged">
<div>git checkout {{.PullRequest.BaseBranch}}</div>
<div>git merge {{$localBranch}}</div>
</div>
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index 1414ac45ee..f4b19f0948 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -6,7 +6,7 @@
<a class="text tw-flex tw-items-center muted">
<strong>{{ctx.Locale.Tr "repo.issues.review.reviewers"}}</strong>
{{if and .CanChooseReviewer (not .Repository.IsArchived)}}
- {{svg "octicon-gear" 16 "gt-ml-2"}}
+ {{svg "octicon-gear" 16 "tw-ml-1"}}
{{end}}
</a>
<div class="filter menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/request_review">
@@ -22,7 +22,7 @@
<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_{{.ItemID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
<span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check"}}</span>
<span class="text">
- {{ctx.AvatarUtils.Avatar .User 28 "gt-mr-3"}}{{template "repo/search_name" .User}}
+ {{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}{{template "repo/search_name" .User}}
</span>
</a>
{{end}}
@@ -37,7 +37,7 @@
<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_team_{{.Team.ID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
<span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check" 16}}</span>
<span class="text">
- {{svg "octicon-people" 16 "gt-ml-4 gt-mr-2"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}
+ {{svg "octicon-people" 16 "tw-ml-4 tw-mr-1"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}
</span>
</a>
{{end}}
@@ -47,19 +47,19 @@
</div>
<div class="ui assignees list">
- <span class="no-select item {{if or .OriginalReviews .PullReviewers}}gt-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_reviewers"}}</span>
+ <span class="no-select item {{if or .OriginalReviews .PullReviewers}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_reviewers"}}</span>
<div class="selected">
{{range .PullReviewers}}
- <div class="item tw-flex tw-items-center gt-py-3">
+ <div class="item tw-flex tw-items-center tw-py-2">
<div class="tw-flex tw-items-center tw-flex-1">
{{if .User}}
- <a class="muted sidebar-item-link" href="{{.User.HomeLink}}">{{ctx.AvatarUtils.Avatar .User 20 "gt-mr-3"}}{{.User.GetDisplayName}}</a>
+ <a class="muted sidebar-item-link" href="{{.User.HomeLink}}">{{ctx.AvatarUtils.Avatar .User 20 "tw-mr-2"}}{{.User.GetDisplayName}}</a>
{{else if .Team}}
- <span class="text">{{svg "octicon-people" 20 "gt-mr-3"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}</span>
+ <span class="text">{{svg "octicon-people" 20 "tw-mr-2"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}</span>
{{end}}
</div>
<div class="tw-flex tw-items-center tw-gap-2">
- {{if (and $.Permission.IsAdmin (or (eq .Review.Type 1) (eq .Review.Type 3)) (not $.Issue.IsClosed))}}
+ {{if (and $.Permission.IsAdmin (or (eq .Review.Type 1) (eq .Review.Type 3)) (not $.Issue.IsClosed) (not $.Issue.PullRequest.HasMerged))}}
<a href="#" class="ui muted icon tw-flex tw-items-center show-modal" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dismiss_review"}}" data-modal="#dismiss-review-modal-{{.Review.ID}}">
{{svg "octicon-x" 20}}
</a>
@@ -91,7 +91,7 @@
{{svg "octicon-hourglass" 16}}
</span>
{{end}}
- {{if .CanChange}}
+ {{if and .CanChange (or .Checked (and (not $.Issue.IsClosed) (not $.Issue.PullRequest.HasMerged)))}}
<a href="#" class="ui muted icon re-request-review{{if .Checked}} checked{{end}}" data-tooltip-content="{{if .Checked}}{{ctx.Locale.Tr "repo.issues.remove_request_review"}}{{else}}{{ctx.Locale.Tr "repo.issues.re_request_review"}}{{end}}" data-issue-id="{{$.Issue.ID}}" data-id="{{.ItemID}}" data-update-url="{{$.RepoLink}}/issues/request_review">{{if .Checked}}{{svg "octicon-trash"}}{{else}}{{svg "octicon-sync"}}{{end}}</a>
{{end}}
{{svg (printf "octicon-%s" .Review.Type.Icon) 16 (printf "text %s" (.Review.HTMLTypeColorName))}}
@@ -99,10 +99,10 @@
</div>
{{end}}
{{range .OriginalReviews}}
- <div class="item tw-flex tw-items-center gt-py-3">
+ <div class="item tw-flex tw-items-center tw-py-2">
<div class="tw-flex tw-items-center tw-flex-1">
<a class="muted" href="{{$.Repository.OriginalURL}}" data-tooltip-content="{{ctx.Locale.Tr "repo.migrated_from_fake" $.Repository.GetOriginalURLHostname}}">
- {{svg (MigrationIcon $.Repository.GetOriginalURLHostname) 20 "gt-mr-3"}}
+ {{svg (MigrationIcon $.Repository.GetOriginalURLHostname) 20 "tw-mr-2"}}
{{.OriginalAuthor}}
</a>
</div>
@@ -136,7 +136,7 @@
<a class="text muted flex-text-block">
<strong>{{ctx.Locale.Tr "repo.issues.new.milestone"}}</strong>
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
- {{svg "octicon-gear" 16 "gt-ml-2"}}
+ {{svg "octicon-gear" 16 "tw-ml-1"}}
{{end}}
</a>
<div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/milestone">
@@ -144,11 +144,11 @@
</div>
</div>
<div class="ui select-milestone list">
- <span class="no-select item {{if .Issue.Milestone}}gt-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_milestone"}}</span>
+ <span class="no-select item {{if .Issue.Milestone}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_milestone"}}</span>
<div class="selected">
{{if .Issue.Milestone}}
<a class="item muted sidebar-item-link" href="{{.RepoLink}}/milestone/{{.Issue.Milestone.ID}}">
- {{svg "octicon-milestone" 18 "gt-mr-3"}}
+ {{svg "octicon-milestone" 18 "tw-mr-2"}}
{{.Issue.Milestone.Name}}
</a>
{{end}}
@@ -161,7 +161,7 @@
<a class="text muted flex-text-block">
<strong>{{ctx.Locale.Tr "repo.issues.new.projects"}}</strong>
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
- {{svg "octicon-gear" 16 "gt-ml-2"}}
+ {{svg "octicon-gear" 16 "tw-ml-1"}}
{{end}}
</a>
<div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/projects">
@@ -184,7 +184,7 @@
</div>
{{range .OpenProjects}}
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
- {{svg .IconName 18 "gt-mr-3"}}{{.Title}}
+ {{svg .IconName 18 "tw-mr-2"}}{{.Title}}
</a>
{{end}}
{{end}}
@@ -195,18 +195,18 @@
</div>
{{range .ClosedProjects}}
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
- {{svg .IconName 18 "gt-mr-3"}}{{.Title}}
+ {{svg .IconName 18 "tw-mr-2"}}{{.Title}}
</a>
{{end}}
{{end}}
</div>
</div>
<div class="ui select-project list">
- <span class="no-select item {{if .Issue.Project}}gt-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_projects"}}</span>
+ <span class="no-select item {{if .Issue.Project}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_projects"}}</span>
<div class="selected">
{{if .Issue.Project}}
<a class="item muted sidebar-item-link" href="{{.Issue.Project.Link ctx}}">
- {{svg .Issue.Project.IconName 18 "gt-mr-3"}}{{.Issue.Project.Title}}
+ {{svg .Issue.Project.IconName 18 "tw-mr-2"}}{{.Issue.Project.Title}}
</a>
{{end}}
</div>
@@ -219,7 +219,7 @@
<a class="text muted flex-text-block">
<strong>{{ctx.Locale.Tr "repo.issues.new.assignees"}}</strong>
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
- {{svg "octicon-gear" 16 "gt-ml-2"}}
+ {{svg "octicon-gear" 16 "tw-ml-1"}}
{{end}}
</a>
<div class="filter menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/assignee">
@@ -240,19 +240,19 @@
{{end}}
<span class="octicon-check {{if not $checked}}tw-invisible{{end}}">{{svg "octicon-check"}}</span>
<span class="text">
- {{ctx.AvatarUtils.Avatar . 20 "gt-mr-3"}}{{template "repo/search_name" .}}
+ {{ctx.AvatarUtils.Avatar . 20 "tw-mr-2"}}{{template "repo/search_name" .}}
</span>
</a>
{{end}}
</div>
</div>
<div class="ui assignees list">
- <span class="no-select item {{if .Issue.Assignees}}gt-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_assignees"}}</span>
+ <span class="no-select item {{if .Issue.Assignees}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_assignees"}}</span>
<div class="selected">
{{range .Issue.Assignees}}
<div class="item">
<a class="muted sidebar-item-link" href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?assignee={{.ID}}">
- {{ctx.AvatarUtils.Avatar . 28 "gt-mr-3"}}
+ {{ctx.AvatarUtils.Avatar . 28 "tw-mr-2"}}
{{.GetDisplayName}}
</a>
</div>
@@ -267,7 +267,7 @@
<div class="ui list tw-flex tw-flex-wrap">
{{range .Participants}}
<a {{if gt .ID 0}}href="{{.HomeLink}}"{{end}} data-tooltip-content="{{.GetDisplayName}}">
- {{ctx.AvatarUtils.Avatar . 28 "gt-my-1 gt-mr-2"}}
+ {{ctx.AvatarUtils.Avatar . 28 "tw-my-0.5 tw-mr-1"}}
</a>
{{end}}
</div>
@@ -278,7 +278,7 @@
<div class="ui watching">
<span class="text"><strong>{{ctx.Locale.Tr "notification.notifications"}}</strong></span>
- <div class="gt-mt-3">
+ <div class="tw-mt-2">
{{template "repo/issue/view_content/watching" .}}
</div>
</div>
@@ -288,7 +288,7 @@
<div class="divider"></div>
<div class="ui timetrack">
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.tracker"}}</strong></span>
- <div class="gt-mt-3">
+ <div class="tw-mt-2">
<form method="post" action="{{.Issue.Link}}/times/stopwatch/toggle" id="toggle_stopwatch_form">
{{$.CsrfTokenHtml}}
</form>
@@ -297,11 +297,11 @@
</form>
{{if $.IsStopwatchRunning}}
<button class="ui fluid button issue-stop-time">
- {{svg "octicon-stopwatch" 16 "gt-mr-3"}}
+ {{svg "octicon-stopwatch" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.issues.stop_tracking"}}
</button>
- <button class="ui fluid button issue-cancel-time gt-mt-3">
- {{svg "octicon-trash" 16 "gt-mr-3"}}
+ <button class="ui fluid button issue-cancel-time tw-mt-2">
+ {{svg "octicon-trash" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.issues.cancel_tracking"}}
</button>
{{else}}
@@ -311,7 +311,7 @@
</div>
{{end}}
<button class="ui fluid button issue-start-time" data-tooltip-content='{{ctx.Locale.Tr "repo.issues.start_tracking"}}'>
- {{svg "octicon-stopwatch" 16 "gt-mr-3"}}
+ {{svg "octicon-stopwatch" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.issues.start_tracking_short"}}
</button>
<div class="ui mini modal issue-start-time-modal">
@@ -328,8 +328,8 @@
<button class="ui cancel button">{{ctx.Locale.Tr "repo.issues.add_time_cancel"}}</button>
</div>
</div>
- <button class="ui fluid button issue-add-time gt-mt-3" data-tooltip-content='{{ctx.Locale.Tr "repo.issues.add_time"}}'>
- {{svg "octicon-plus" 16 "gt-mr-3"}}
+ <button class="ui fluid button issue-add-time tw-mt-2" data-tooltip-content='{{ctx.Locale.Tr "repo.issues.add_time"}}'>
+ {{svg "octicon-plus" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.issues.add_time_short"}}
</button>
{{end}}
@@ -342,7 +342,7 @@
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.time_spent_from_all_authors" ($.Issue.TotalTrackedTime | Sec2Time)}}</strong></span>
<div>
{{range $user, $trackedtime := .WorkingUsers}}
- <div class="comment gt-mt-3">
+ <div class="comment tw-mt-2">
<a class="avatar">
{{ctx.AvatarUtils.Avatar $user}}
</a>
@@ -362,7 +362,7 @@
<div class="divider"></div>
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.due_date"}}</strong></span>
<div class="ui form" id="deadline-loader">
- <div class="ui negative message gt-hidden" id="deadline-err-invalid-date">
+ <div class="ui negative message tw-hidden" id="deadline-err-invalid-date">
{{svg "octicon-x" 16 "close icon"}}
{{ctx.Locale.Tr "repo.issues.due_date_invalid"}}
</div>
@@ -370,12 +370,12 @@
<p>
<div class="tw-flex tw-justify-between tw-items-center">
<div class="due-date {{if .Issue.IsOverdue}}text red{{end}}" {{if .Issue.IsOverdue}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_overdue"}}"{{end}}>
- {{svg "octicon-calendar" 16 "gt-mr-3"}}
+ {{svg "octicon-calendar" 16 "tw-mr-2"}}
{{DateTime "long" .Issue.DeadlineUnix.FormatDate}}
</div>
<div>
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
- <a class="issue-due-edit muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_edit"}}">{{svg "octicon-pencil" 16 "gt-mr-2"}}</a>
+ <a class="issue-due-edit muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_edit"}}">{{svg "octicon-pencil" 16 "tw-mr-1"}}</a>
<a class="issue-due-remove muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_remove"}}">{{svg "octicon-trash"}}</a>
{{end}}
</div>
@@ -386,7 +386,7 @@
{{end}}
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
- <div {{if ne .Issue.DeadlineUnix 0}} class="gt-hidden"{{end}} id="deadlineForm">
+ <div {{if ne .Issue.DeadlineUnix 0}} class="tw-hidden"{{end}} id="deadlineForm">
<form class="ui fluid action input issue-due-form" action="{{AppSubUrl}}/{{PathEscape .Repository.Owner.Name}}/{{PathEscape .Repository.Name}}/issues/{{.Issue.Index}}/deadline" method="post" id="update-issue-deadline-form">
{{$.CsrfTokenHtml}}
<input required placeholder="{{ctx.Locale.Tr "repo.issues.due_date_form"}}" {{if gt .Issue.DeadlineUnix 0}}value="{{.Issue.DeadlineUnix.FormatDate}}"{{end}} type="date" name="deadlineDate" id="deadlineDate">
@@ -433,7 +433,7 @@
{{.Repository.OwnerName}}/{{.Repository.Name}}
</div>
</div>
- <div class="item-right tw-flex tw-items-center gt-m-2">
+ <div class="item-right tw-flex tw-items-center tw-m-1">
{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blocking" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
{{svg "octicon-trash" 16}}
@@ -465,7 +465,7 @@
{{.Repository.OwnerName}}/{{.Repository.Name}}
</div>
</div>
- <div class="item-right tw-flex tw-items-center gt-m-2">
+ <div class="item-right tw-flex tw-items-center tw-m-1">
{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blockedBy" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
{{svg "octicon-trash" 16}}
@@ -488,7 +488,7 @@
{{.Repository.OwnerName}}/{{.Repository.Name}}
</div>
</div>
- <div class="item-right tw-flex tw-items-center gt-m-2">
+ <div class="item-right tw-flex tw-items-center tw-m-1">
{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blocking" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
{{svg "octicon-trash" 16}}
@@ -557,7 +557,7 @@
{{$issueReferenceLink := printf "%s#%d" .Issue.Repo.FullName .Issue.Index}}
<div class="row tw-items-center" data-tooltip-content="{{$issueReferenceLink}}">
<span class="text column truncate">{{ctx.Locale.Tr "repo.issues.reference_link" $issueReferenceLink}}</span>
- <button class="ui two wide button column gt-p-3" data-clipboard-text="{{$issueReferenceLink}}">{{svg "octicon-copy" 14}}</button>
+ <button class="ui two wide button column tw-p-2" data-clipboard-text="{{$issueReferenceLink}}">{{svg "octicon-copy" 14}}</button>
</div>
</div>
@@ -565,21 +565,21 @@
<div class="divider"></div>
{{if or .PinEnabled .Issue.IsPinned}}
- <form class="gt-mt-2 form-fetch-action single-button-form" method="post" {{if $.NewPinAllowed}}action="{{.Issue.Link}}/pin"{{else}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.max_pinned"}}"{{end}}>
+ <form class="tw-mt-1 form-fetch-action single-button-form" method="post" {{if $.NewPinAllowed}}action="{{.Issue.Link}}/pin"{{else}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.max_pinned"}}"{{end}}>
{{$.CsrfTokenHtml}}
<button class="fluid ui button {{if not $.NewPinAllowed}}disabled{{end}}">
{{if not .Issue.IsPinned}}
- {{svg "octicon-pin" 16 "gt-mr-3"}}
+ {{svg "octicon-pin" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "pin"}}
{{else}}
- {{svg "octicon-pin-slash" 16 "gt-mr-3"}}
+ {{svg "octicon-pin-slash" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "unpin"}}
{{end}}
</button>
</form>
{{end}}
- <button class="gt-mt-2 fluid ui show-modal button {{if .Issue.IsLocked}} negative {{end}}" data-modal="#lock">
+ <button class="tw-mt-1 fluid ui show-modal button {{if .Issue.IsLocked}} negative {{end}}" data-modal="#lock">
{{if .Issue.IsLocked}}
{{svg "octicon-key"}}
{{ctx.Locale.Tr "repo.issues.unlock"}}
@@ -652,7 +652,7 @@
</form>
</div>
</div>
- <button class="gt-mt-2 fluid ui show-modal button" data-modal="#sidebar-delete-issue">
+ <button class="tw-mt-1 fluid ui show-modal button" data-modal="#sidebar-delete-issue">
{{svg "octicon-trash"}}
{{ctx.Locale.Tr "repo.issues.delete"}}
</button>
diff --git a/templates/repo/issue/view_content/watching.tmpl b/templates/repo/issue/view_content/watching.tmpl
index 0e8562fed2..05936d090b 100644
--- a/templates/repo/issue/view_content/watching.tmpl
+++ b/templates/repo/issue/view_content/watching.tmpl
@@ -2,10 +2,10 @@
<input type="hidden" name="watch" value="{{if $.IssueWatch.IsWatching}}0{{else}}1{{end}}">
<button class="fluid ui button">
{{if $.IssueWatch.IsWatching}}
- {{svg "octicon-mute" 16 "gt-mr-3"}}
+ {{svg "octicon-mute" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.issues.unsubscribe"}}
{{else}}
- {{svg "octicon-unmute" 16 "gt-mr-3"}}
+ {{svg "octicon-unmute" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.issues.subscribe"}}
{{end}}
</button>
diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl
index f65a4ee085..3a5ba0f4a3 100644
--- a/templates/repo/issue/view_title.tmpl
+++ b/templates/repo/issue/view_title.tmpl
@@ -1,5 +1,5 @@
{{if .Flash}}
- <div class="sixteen wide column gt-mb-3">
+ <div class="sixteen wide column tw-mb-2">
{{template "base/alert" .}}
</div>
{{end}}
@@ -8,28 +8,28 @@
<h1 class="gt-word-break">
<span id="issue-title">{{RenderIssueTitle $.Context .Issue.Title ($.Repository.ComposeMetas ctx) | RenderCodeBlock}} <span class="index">#{{.Issue.Index}}</span>
</span>
- <div id="edit-title-input" class="ui input tw-flex-1 gt-hidden">
+ <div id="edit-title-input" class="ui input tw-flex-1 tw-hidden">
<input value="{{.Issue.Title}}" maxlength="255" autocomplete="off">
</div>
</h1>
<div class="issue-title-buttons">
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}}
- <button id="edit-title" class="ui small basic button edit-button not-in-edit{{if .Issue.IsPull}} gt-mr-0{{end}}">{{ctx.Locale.Tr "repo.issues.edit"}}</button>
+ <button id="edit-title" class="ui small basic button edit-button not-in-edit tw-mr-0">{{ctx.Locale.Tr "repo.issues.edit"}}</button>
{{end}}
{{if not .Issue.IsPull}}
- <a role="button" class="ui small primary button new-issue-button gt-mr-0" href="{{.RepoLink}}/issues/new{{if .NewIssueChooseTemplate}}/choose{{end}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
+ <a role="button" class="ui small primary button new-issue-button tw-mr-0" href="{{.RepoLink}}/issues/new{{if .NewIssueChooseTemplate}}/choose{{end}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
{{end}}
</div>
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}}
<div class="edit-buttons">
- <button id="cancel-edit-title" class="ui small basic button in-edit gt-hidden">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
- <button id="save-edit-title" class="ui small primary button in-edit gt-hidden gt-mr-0" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/title" {{if .Issue.IsPull}}data-target-update-url="{{$.RepoLink}}/pull/{{.Issue.Index}}/target_branch"{{end}}>{{ctx.Locale.Tr "repo.issues.save"}}</button>
+ <button id="cancel-edit-title" class="ui small basic button in-edit tw-hidden">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
+ <button id="save-edit-title" class="ui small primary button in-edit tw-hidden tw-mr-0" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/title" {{if .Issue.IsPull}}data-target-update-url="{{$.RepoLink}}/pull/{{.Issue.Index}}/target_branch"{{end}}>{{ctx.Locale.Tr "repo.issues.save"}}</button>
</div>
{{end}}
</div>
<div class="issue-title-meta">
{{if .HasMerged}}
- <div class="ui purple label issue-state-label">{{svg "octicon-git-merge" 16 "gt-mr-2"}} {{if eq .Issue.PullRequest.Status 3}}{{ctx.Locale.Tr "repo.pulls.manually_merged"}}{{else}}{{ctx.Locale.Tr "repo.pulls.merged"}}{{end}}</div>
+ <div class="ui purple label issue-state-label">{{svg "octicon-git-merge" 16 "tw-mr-1"}} {{if eq .Issue.PullRequest.Status 3}}{{ctx.Locale.Tr "repo.pulls.manually_merged"}}{{else}}{{ctx.Locale.Tr "repo.pulls.merged"}}{{end}}</div>
{{else if .Issue.IsClosed}}
<div class="ui red label issue-state-label">{{if .Issue.IsPull}}{{svg "octicon-git-pull-request"}}{{else}}{{svg "octicon-issue-closed"}}{{end}} {{ctx.Locale.Tr "repo.issues.closed_title"}}</div>
{{else if .Issue.IsPull}}
@@ -41,7 +41,7 @@
{{else}}
<div class="ui green label issue-state-label">{{svg "octicon-issue-opened"}} {{ctx.Locale.Tr "repo.issues.open_title"}}</div>
{{end}}
- <div class="gt-ml-3">
+ <div class="tw-ml-2">
{{if .Issue.IsPull}}
{{$headHref := .HeadTarget}}
{{if .HeadBranchLink}}
@@ -81,9 +81,9 @@
</span>
</a>
{{end}}
- <span id="pull-desc-edit" class="gt-hidden flex-text-block">
+ <span id="pull-desc-edit" class="tw-hidden flex-text-block">
<div class="ui floating filter dropdown">
- <div class="ui basic small button gt-mr-0">
+ <div class="ui basic small button tw-mr-0">
<span class="text">{{ctx.Locale.Tr "repo.pulls.compare_compare"}}: {{$.HeadTarget}}</span>
</div>
</div>
diff --git a/templates/repo/latest_commit.tmpl b/templates/repo/latest_commit.tmpl
index b2f0798917..f945e9dfa1 100644
--- a/templates/repo/latest_commit.tmpl
+++ b/templates/repo/latest_commit.tmpl
@@ -2,7 +2,7 @@
<div class="ui active tiny slow centered inline">…</div>
{{else}}
{{if .LatestCommitUser}}
- {{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "gt-mr-2"}}
+ {{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "tw-mr-1"}}
{{if .LatestCommitUser.FullName}}
<a class="muted author-wrapper" title="{{.LatestCommitUser.FullName}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{.LatestCommitUser.FullName}}</strong></a>
{{else}}
@@ -10,7 +10,7 @@
{{end}}
{{else}}
{{if .LatestCommit.Author}}
- {{ctx.AvatarUtils.AvatarByEmail .LatestCommit.Author.Email .LatestCommit.Author.Name 24 "gt-mr-2"}}
+ {{ctx.AvatarUtils.AvatarByEmail .LatestCommit.Author.Email .LatestCommit.Author.Name 24 "tw-mr-1"}}
<span class="author-wrapper" title="{{.LatestCommit.Author.Name}}"><strong>{{.LatestCommit.Author.Name}}</strong></span>
{{end}}
{{end}}
@@ -25,7 +25,7 @@
<span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span class="message-wrapper">{{RenderCommitMessageLinkSubject $.Context .LatestCommit.Message $commitLink ($.Repository.ComposeMetas ctx)}}</span>
{{if IsMultilineCommitMessage .LatestCommit.Message}}
<button class="ui button js-toggle-commit-body ellipsis-button" aria-expanded="false">...</button>
- <pre class="commit-body gt-hidden">{{RenderCommitBody $.Context .LatestCommit.Message ($.Repository.ComposeMetas ctx)}}</pre>
+ <pre class="commit-body tw-hidden">{{RenderCommitBody $.Context .LatestCommit.Message ($.Repository.ComposeMetas ctx)}}</pre>
{{end}}
</span>
{{end}}
diff --git a/templates/repo/migrate/gitea.tmpl b/templates/repo/migrate/gitea.tmpl
index 143f220449..f2a3ae4348 100644
--- a/templates/repo/migrate/gitea.tmpl
+++ b/templates/repo/migrate/gitea.tmpl
@@ -21,7 +21,7 @@
<div class="inline field {{if .Err_Auth}}error{{end}}">
<label for="auth_token">{{ctx.Locale.Tr "access_token"}}</label>
<input id="auth_token" name="auth_token" type="password" autocomplete="new-password" value="{{.auth_token}}" {{if not .auth_token}} data-need-clear="true" {{end}}>
- <a target="_blank" href="https://docs.gitea.com/development/api-usage">{{svg "octicon-question"}}</a>
+ <a target="_blank" href="https://forgejo.org/docs/latest/user/api-usage/">{{svg "octicon-question"}}</a>
</div>
{{template "repo/migrate/options" .}}
diff --git a/templates/repo/migrate/migrate.tmpl b/templates/repo/migrate/migrate.tmpl
index 32465bc394..c5c697edff 100644
--- a/templates/repo/migrate/migrate.tmpl
+++ b/templates/repo/migrate/migrate.tmpl
@@ -7,11 +7,11 @@
{{range .Services}}
<a class="ui card migrate-entry tw-flex tw-items-center" href="{{AppSubUrl}}/repo/migrate?service_type={{.}}&org={{$.Org}}&mirror={{$.Mirror}}">
{{if eq .Name "github"}}
- {{svg "octicon-mark-github" 184 "gt-p-4"}}
+ {{svg "octicon-mark-github" 184 "tw-p-4"}}
{{else if eq .Name "gitlab"}}
- {{svg "gitea-gitlab" 184 "gt-p-4"}}
+ {{svg "gitea-gitlab" 184 "tw-p-4"}}
{{else if eq .Name "gitbucket"}}
- {{svg "gitea-gitbucket" 184 "gt-p-4"}}
+ {{svg "gitea-gitbucket" 184 "tw-p-4"}}
{{else}}
{{svg (printf "gitea-%s" .Name) 184}}
{{end}}
diff --git a/templates/repo/migrate/migrating.tmpl b/templates/repo/migrate/migrating.tmpl
index 4d7e45f2e0..0b53636160 100644
--- a/templates/repo/migrate/migrating.tmpl
+++ b/templates/repo/migrate/migrating.tmpl
@@ -12,7 +12,7 @@
<img src="{{AssetUrlPrefix}}/img/forgejo-loading.svg" width="256" height="256">
</div>
</div>
- <div id="repo_migrating_failed_image" class="sixteen wide center aligned centered column gt-hidden">
+ <div id="repo_migrating_failed_image" class="sixteen wide center aligned centered column tw-hidden">
<div>
<span class="red">{{svg "octicon-git-pull-request-closed" 256 "ui red icon"}}</span>
</div>
@@ -24,7 +24,7 @@
<p>{{ctx.Locale.Tr "repo.migrate.migrating" .CloneAddr}}</p>
<p id="repo_migrating_progress_message"></p>
</div>
- <div id="repo_migrating_failed" class="gt-hidden">
+ <div id="repo_migrating_failed" class="tw-hidden">
{{if .CloneAddr}}
<p>{{ctx.Locale.Tr "repo.migrate.migrating_failed" .CloneAddr}}</p>
{{else}}
@@ -40,7 +40,7 @@
{{else}}
<button class="ui basic show-modal button" data-modal="#cancel-repo-modal">{{ctx.Locale.Tr "cancel"}}</button>
{{end}}
- <button id="repo_migrating_retry" data-migrating-task-retry-url="{{.Link}}/settings/migrate/retry" class="ui basic button gt-hidden">{{ctx.Locale.Tr "retry"}}</button>
+ <button id="repo_migrating_retry" data-migrating-task-retry-url="{{.Link}}/settings/migrate/retry" class="ui basic button tw-hidden">{{ctx.Locale.Tr "retry"}}</button>
</div>
{{end}}
</div>
diff --git a/templates/repo/migrate/options.tmpl b/templates/repo/migrate/options.tmpl
index 1cf8600749..8a46e5769b 100644
--- a/templates/repo/migrate/options.tmpl
+++ b/templates/repo/migrate/options.tmpl
@@ -14,9 +14,9 @@
<input id="lfs" name="lfs" type="checkbox" {{if .lfs}} checked{{end}}>
<label>{{ctx.Locale.Tr "repo.migrate_options_lfs"}}</label>
</div>
- <span id="lfs_settings" class="gt-hidden">(<a id="lfs_settings_show" href="#">{{ctx.Locale.Tr "repo.settings.advanced_settings"}}</a>)</span>
+ <span id="lfs_settings" class="tw-hidden">(<a id="lfs_settings_show" href="#">{{ctx.Locale.Tr "repo.settings.advanced_settings"}}</a>)</span>
</div>
-<div id="lfs_endpoint" class="gt-hidden">
+<div id="lfs_endpoint" class="tw-hidden">
<span class="help">{{ctx.Locale.Tr "repo.migrate_options_lfs_endpoint.description" "https://github.com/git-lfs/git-lfs/blob/main/docs/api/server-discovery.md#server-discovery"}}{{if .ContextUser.CanImportLocal}} {{ctx.Locale.Tr "repo.migrate_options_lfs_endpoint.description.local"}}{{end}}</span>
<div class="inline field {{if .Err_LFSEndpoint}}error{{end}}">
<label>{{ctx.Locale.Tr "repo.migrate_options_lfs_endpoint.label"}}</label>
diff --git a/templates/repo/projects/view.tmpl b/templates/repo/projects/view.tmpl
index eea1057a50..05ad7264bf 100644
--- a/templates/repo/projects/view.tmpl
+++ b/templates/repo/projects/view.tmpl
@@ -2,7 +2,7 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository projects view-project">
{{template "repo/header" .}}
<div class="ui container padded">
- <div class="tw-flex tw-justify-between tw-items-center gt-mb-4">
+ <div class="tw-flex tw-justify-between tw-items-center tw-mb-4">
{{template "repo/issue/navbar" .}}
<a class="ui small primary button" href="{{.RepoLink}}/issues/new/choose?project={{.Project.ID}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
</div>
diff --git a/templates/repo/pulls/tab_menu.tmpl b/templates/repo/pulls/tab_menu.tmpl
index 0ddb17a934..c0e48928f9 100644
--- a/templates/repo/pulls/tab_menu.tmpl
+++ b/templates/repo/pulls/tab_menu.tmpl
@@ -15,7 +15,7 @@
{{ctx.Locale.Tr "repo.pulls.tab_files"}}
<span class="ui small label">{{if .NumFiles}}{{.NumFiles}}{{else}}-{{end}}</span>
</a>
- <span class="item tw-ml-auto gt-pr-0 tw-font-bold tw-flex tw-items-center tw-gap-2">
+ <span class="item tw-ml-auto tw-pr-0 tw-font-bold tw-flex tw-items-center tw-gap-2">
<span><span class="text green">{{if .Diff.TotalAddition}}+{{.Diff.TotalAddition}}{{end}}</span> <span class="text red">{{if .Diff.TotalDeletion}}-{{.Diff.TotalDeletion}}{{end}}</span></span>
<span class="diff-stats-bar">
<div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .Diff.TotalAddition "/" "(" .Diff.TotalAddition "+" .Diff.TotalDeletion "+" 0.0 ")"}}%"></div>
diff --git a/templates/repo/pulse.tmpl b/templates/repo/pulse.tmpl
index 5943ae0434..cfb3ec1d3d 100644
--- a/templates/repo/pulse.tmpl
+++ b/templates/repo/pulse.tmpl
@@ -109,7 +109,7 @@
{{if gt .Activity.PublishedReleaseCount 0}}
<h4 class="divider divider-text tw-normal-case" id="published-releases">
- {{svg "octicon-tag" 16 "gt-mr-3"}}
+ {{svg "octicon-tag" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.activity.title.releases_published_by"
(ctx.Locale.TrN .Activity.PublishedReleaseCount "repo.activity.title.releases_1" "repo.activity.title.releases_n" .Activity.PublishedReleaseCount)
(ctx.Locale.TrN .Activity.PublishedReleaseAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.PublishedReleaseAuthorCount)
@@ -131,7 +131,7 @@
{{if gt .Activity.MergedPRCount 0}}
<h4 class="divider divider-text tw-normal-case" id="merged-pull-requests">
- {{svg "octicon-git-pull-request" 16 "gt-mr-3"}}
+ {{svg "octicon-git-pull-request" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.activity.title.prs_merged_by"
(ctx.Locale.TrN .Activity.MergedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.MergedPRCount)
(ctx.Locale.TrN .Activity.MergedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.MergedPRAuthorCount)
@@ -150,7 +150,7 @@
{{if gt .Activity.OpenedPRCount 0}}
<h4 class="divider divider-text tw-normal-case" id="proposed-pull-requests">
- {{svg "octicon-git-branch" 16 "gt-mr-3"}}
+ {{svg "octicon-git-branch" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.activity.title.prs_opened_by"
(ctx.Locale.TrN .Activity.OpenedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.OpenedPRCount)
(ctx.Locale.TrN .Activity.OpenedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.OpenedPRAuthorCount)
@@ -169,7 +169,7 @@
{{if gt .Activity.ClosedIssueCount 0}}
<h4 class="divider divider-text tw-normal-case" id="closed-issues">
- {{svg "octicon-issue-closed" 16 "gt-mr-3"}}
+ {{svg "octicon-issue-closed" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.activity.title.issues_closed_from"
(ctx.Locale.TrN .Activity.ClosedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.ClosedIssueCount)
(ctx.Locale.TrN .Activity.ClosedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.ClosedIssueAuthorCount)
@@ -188,7 +188,7 @@
{{if gt .Activity.OpenedIssueCount 0}}
<h4 class="divider divider-text tw-normal-case" id="new-issues">
- {{svg "octicon-issue-opened" 16 "gt-mr-3"}}
+ {{svg "octicon-issue-opened" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.activity.title.issues_created_by"
(ctx.Locale.TrN .Activity.OpenedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.OpenedIssueCount)
(ctx.Locale.TrN .Activity.OpenedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.OpenedIssueAuthorCount)
@@ -207,7 +207,7 @@
{{if gt .Activity.UnresolvedIssueCount 0}}
<h4 class="divider divider-text tw-normal-case" id="unresolved-conversations" data-tooltip-content="{{ctx.Locale.Tr "repo.activity.unresolved_conv_desc"}}">
- {{svg "octicon-comment-discussion" 16 "gt-mr-3"}}
+ {{svg "octicon-comment-discussion" 16 "tw-mr-2"}}
{{ctx.Locale.TrN .Activity.UnresolvedIssueCount "repo.activity.title.unresolved_conv_1" "repo.activity.title.unresolved_conv_n" .Activity.UnresolvedIssueCount}}
</h4>
<div class="list">
diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl
index 8c64380d45..f3b4bc8443 100644
--- a/templates/repo/release/list.tmpl
+++ b/templates/repo/release/list.tmpl
@@ -9,14 +9,14 @@
{{$release := $info.Release}}
<li class="ui grid">
<div class="ui four wide column meta">
- <a class="muted" href="{{if not (and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode))}}#{{else}}{{$.RepoLink}}/src/tag/{{$release.TagName | PathEscapeSegments}}{{end}}" rel="nofollow">{{svg "octicon-tag" 16 "gt-mr-2"}}{{$release.TagName}}</a>
+ <a class="muted" href="{{if not (and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode))}}#{{else}}{{$.RepoLink}}/src/tag/{{$release.TagName | PathEscapeSegments}}{{end}}" rel="nofollow">{{svg "octicon-tag" 16 "tw-mr-1"}}{{$release.TagName}}</a>
{{if and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode)}}
- <a class="muted gt-mono" href="{{$.RepoLink}}/src/commit/{{$release.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha $release.Sha1}}</a>
+ <a class="muted tw-font-mono" href="{{$.RepoLink}}/src/commit/{{$release.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha $release.Sha1}}</a>
{{template "repo/branch_dropdown" dict "root" $ "release" $release}}
{{end}}
</div>
<div class="ui twelve wide column detail">
- <div class="tw-flex tw-items-center tw-justify-between tw-flex-wrap gt-mb-3">
+ <div class="tw-flex tw-items-center tw-justify-between tw-flex-wrap tw-mb-2">
<h4 class="release-list-title gt-word-break">
<a href="{{$.RepoLink}}/releases/tag/{{$release.TagName | PathEscapeSegments}}">{{$release.Title}}</a>
{{template "repo/commit_statuses" dict "Status" $info.CommitStatus "Statuses" $info.CommitStatuses "AdditionalClasses" "tw-flex"}}
@@ -39,9 +39,9 @@
<p class="text grey">
<span class="author">
{{if $release.OriginalAuthor}}
- {{svg (MigrationIcon $release.Repo.GetOriginalURLHostname) 20 "gt-mr-2"}}{{$release.OriginalAuthor}}
+ {{svg (MigrationIcon $release.Repo.GetOriginalURLHostname) 20 "tw-mr-1"}}{{$release.OriginalAuthor}}
{{else if $release.Publisher}}
- {{ctx.AvatarUtils.Avatar $release.Publisher 20 "gt-mr-2"}}
+ {{ctx.AvatarUtils.Avatar $release.Publisher 20 "tw-mr-1"}}
<a href="{{$release.Publisher.HomeLink}}">{{$release.Publisher.GetDisplayName}}</a>
{{else}}
Ghost
@@ -60,24 +60,25 @@
<div class="markup desc">
{{$release.RenderedNote}}
</div>
+ {{template "repo/tag/verification_line" (dict "ctxData" $ "release" $release)}}
<div class="divider"></div>
<details class="download" {{if eq $idx 0}}open{{end}}>
- <summary class="gt-my-4">
+ <summary class="tw-my-4">
{{ctx.Locale.Tr "repo.release.downloads"}}
</summary>
<ul class="list">
{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
<li>
- <a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong></a>
+ <a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong></a>
</li>
<li>
- <a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong></a>
+ <a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong></a>
</li>
{{end}}
{{range $release.Attachments}}
<li>
<a target="_blank" rel="nofollow" href="{{.DownloadURL}}" download>
- <strong>{{svg "octicon-package" 16 "gt-mr-2"}}{{.Name}}</strong>
+ <strong>{{svg "octicon-package" 16 "tw-mr-1"}}{{.Name}}</strong>
</a>
<div>
<span class="text grey">{{.Size | FileSize}}</span>
diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl
index fd6338a701..c01f9a421b 100644
--- a/templates/repo/release/new.tmpl
+++ b/templates/repo/release/new.tmpl
@@ -39,12 +39,12 @@
</div>
</div>
<div>
- <span id="tag-helper" class="help gt-mt-3 gt-pb-0">{{ctx.Locale.Tr "repo.release.tag_helper"}}</span>
+ <span id="tag-helper" class="help tw-mt-2 tw-pb-0">{{ctx.Locale.Tr "repo.release.tag_helper"}}</span>
</div>
{{end}}
</div>
</div>
- <div class="eleven wide gt-pt-0">
+ <div class="eleven wide tw-pt-0">
<div class="field {{if .Err_Title}}error{{end}}">
<input name="title" aria-label="{{ctx.Locale.Tr "repo.release.title"}}" placeholder="{{ctx.Locale.Tr "repo.release.title"}}" value="{{.title}}" autofocus maxlength="255">
</div>
@@ -100,7 +100,7 @@
</div>
</div>
<span class="help">{{ctx.Locale.Tr "repo.release.prerelease_helper"}}</span>
- <div class="divider gt-mt-0"></div>
+ <div class="divider tw-mt-0"></div>
<div class="tw-flex tw-justify-end">
{{if .PageIsEditRelease}}
<a class="ui small button" href="{{.RepoLink}}/releases">
diff --git a/templates/repo/release_tag_header.tmpl b/templates/repo/release_tag_header.tmpl
index 7a00cbe49c..5896fdd19d 100644
--- a/templates/repo/release_tag_header.tmpl
+++ b/templates/repo/release_tag_header.tmpl
@@ -4,7 +4,7 @@
{{if $canReadReleases}}
<div class="tw-flex">
<div class="tw-flex-1 tw-flex tw-items-center">
- <h2 class="ui compact small menu header small-menu-items">
+ <h2 class="ui compact small menu small-menu-items">
<a class="{{if and .PageIsReleaseList (not .PageIsSingleTag)}}active {{end}}item" href="{{.RepoLink}}/releases">{{ctx.Locale.PrettyNumber .NumReleases}} {{ctx.Locale.TrN .NumReleases "repo.release" "repo.releases"}}</a>
{{if $canReadCode}}
<a class="{{if or .PageIsTagList .PageIsSingleTag}}active {{end}}item" href="{{.RepoLink}}/tags">{{ctx.Locale.PrettyNumber .NumTags}} {{ctx.Locale.TrN .NumTags "repo.tag" "repo.tags"}}</a>
diff --git a/templates/repo/settings/branches.tmpl b/templates/repo/settings/branches.tmpl
index ee6bdfbf2f..52c0c2c800 100644
--- a/templates/repo/settings/branches.tmpl
+++ b/templates/repo/settings/branches.tmpl
@@ -16,7 +16,7 @@
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="default_branch">
{{if not .Repository.IsEmpty}}
- <div class="ui dropdown selection search tw-flex-1 gt-mr-3 tw-max-w-96">
+ <div class="ui dropdown selection search tw-flex-1 tw-mr-2 tw-max-w-96">
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<input type="hidden" name="branch" value="{{.Repository.DefaultBranch}}">
<div class="default text">{{.Repository.DefaultBranch}}</div>
diff --git a/templates/repo/settings/deploy_keys.tmpl b/templates/repo/settings/deploy_keys.tmpl
index f66b94c332..da1a321785 100644
--- a/templates/repo/settings/deploy_keys.tmpl
+++ b/templates/repo/settings/deploy_keys.tmpl
@@ -11,7 +11,7 @@
</div>
</h4>
<div class="ui attached segment">
- <div class="{{if not .HasError}}gt-hidden{{end}} gt-mb-4" id="add-deploy-key-panel">
+ <div class="{{if not .HasError}}tw-hidden{{end}} tw-mb-4" id="add-deploy-key-panel">
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="field">
diff --git a/templates/repo/settings/githook_edit.tmpl b/templates/repo/settings/githook_edit.tmpl
index db8982a282..e20f51b922 100644
--- a/templates/repo/settings/githook_edit.tmpl
+++ b/templates/repo/settings/githook_edit.tmpl
@@ -14,7 +14,7 @@
</div>
<div class="field">
<label for="content">{{ctx.Locale.Tr "repo.settings.githook_content"}}</label>
- <textarea id="content" name="content" class="gt-hidden">{{if .IsActive}}{{.Content}}{{else}}{{.Sample}}{{end}}</textarea>
+ <textarea id="content" name="content" class="tw-hidden">{{if .IsActive}}{{.Content}}{{else}}{{.Sample}}{{end}}</textarea>
<div class="editor-loading is-loading"></div>
</div>
<div class="inline field">
diff --git a/templates/repo/settings/githooks.tmpl b/templates/repo/settings/githooks.tmpl
index 3d15d097cc..1a603f9fe8 100644
--- a/templates/repo/settings/githooks.tmpl
+++ b/templates/repo/settings/githooks.tmpl
@@ -10,9 +10,9 @@
</div>
{{range .Hooks}}
<div class="item truncated-item-container">
- <span class="text {{if .IsActive}}green{{else}}grey{{end}} gt-mr-3">{{svg "octicon-dot-fill" 22}}</span>
- <span class="text truncate tw-flex-1 gt-mr-3">{{.Name}}</span>
- <a class="muted tw-float-right gt-p-3" href="{{$.RepoLink}}/settings/hooks/git/{{.Name|PathEscape}}">
+ <span class="text {{if .IsActive}}green{{else}}grey{{end}} tw-mr-2">{{svg "octicon-dot-fill" 22}}</span>
+ <span class="text truncate tw-flex-1 tw-mr-2">{{.Name}}</span>
+ <a class="muted tw-float-right tw-p-2" href="{{$.RepoLink}}/settings/hooks/git/{{.Name|PathEscape}}">
{{svg "octicon-pencil"}}
</a>
</div>
diff --git a/templates/repo/settings/lfs.tmpl b/templates/repo/settings/lfs.tmpl
index dca4d1f1ce..e0864ff221 100644
--- a/templates/repo/settings/lfs.tmpl
+++ b/templates/repo/settings/lfs.tmpl
@@ -12,7 +12,7 @@
{{range .LFSFiles}}
<tr>
<td>
- <a href="{{$.Link}}/show/{{.Oid}}" title="{{.Oid}}" class="ui brown button gt-mono">
+ <a href="{{$.Link}}/show/{{.Oid}}" title="{{.Oid}}" class="ui brown button tw-font-mono">
{{ShortSha .Oid}}
</a>
</td>
diff --git a/templates/repo/settings/lfs_file.tmpl b/templates/repo/settings/lfs_file.tmpl
index 7f1d07e46f..43afba96c3 100644
--- a/templates/repo/settings/lfs_file.tmpl
+++ b/templates/repo/settings/lfs_file.tmpl
@@ -5,7 +5,7 @@
<a href="{{.LFSFilesLink}}">{{ctx.Locale.Tr "repo.settings.lfs"}}</a> / <span class="truncate sha">{{.LFSFile.Oid}}</span>
<div class="ui right">
{{if .EscapeStatus.Escaped}}
- <a class="ui tiny basic button unescape-button gt-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a>
+ <a class="ui tiny basic button unescape-button tw-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a>
<a class="ui tiny basic button escape-button">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a>
{{end}}
<a class="ui primary tiny button" href="{{.LFSFilesLink}}/find?oid={{.LFSFile.Oid}}&size={{.LFSFile.Size}}">{{ctx.Locale.Tr "repo.settings.lfs_findcommits"}}</a>
diff --git a/templates/repo/settings/lfs_pointers.tmpl b/templates/repo/settings/lfs_pointers.tmpl
index fa2e376ff3..a0bb8c46f0 100644
--- a/templates/repo/settings/lfs_pointers.tmpl
+++ b/templates/repo/settings/lfs_pointers.tmpl
@@ -32,12 +32,12 @@
{{range .Pointers}}
<tr>
<td>
- <a href="{{$.RepoLink}}/raw/blob/{{.SHA}}" rel="nofollow" target="_blank" title="{{.SHA}}" class="ui button gt-mono">
+ <a href="{{$.RepoLink}}/raw/blob/{{.SHA}}" rel="nofollow" target="_blank" title="{{.SHA}}" class="ui button tw-font-mono">
{{ShortSha .SHA}}
</a>
</td>
<td>
- <a {{if and .Exists .InRepo}}href="{{$.LFSFilesLink}}/show/{{.Oid}}" rel="nofollow" target="_blank"{{end}} title="{{.Oid}}" class="ui brown button gt-mono">
+ <a {{if and .Exists .InRepo}}href="{{$.LFSFilesLink}}/show/{{.Oid}}" rel="nofollow" target="_blank"{{end}} title="{{.Oid}}" class="ui brown button tw-font-mono">
{{ShortSha .Oid}}
</a>
</td>
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl
index 7827f0b63b..21a5471cb8 100644
--- a/templates/repo/settings/options.tmpl
+++ b/templates/repo/settings/options.tmpl
@@ -121,7 +121,7 @@
<tbody>
<tr>
<td colspan="4">
- <div class="text red gt-py-4">{{ctx.Locale.Tr "repo.settings.mirror_settings.direction.pull"}}: {{ctx.Locale.Tr "error.occurred"}}</div>
+ <div class="text red tw-py-4">{{ctx.Locale.Tr "repo.settings.mirror_settings.direction.pull"}}: {{ctx.Locale.Tr "error.occurred"}}</div>
</td>
</tr>
</tbody>
@@ -163,10 +163,10 @@
<p class="help">{{ctx.Locale.Tr "repo.mirror_address_desc"}}</p>
</div>
<details class="ui optional field" {{if or .Err_Auth $address.Username}}open{{end}}>
- <summary class="gt-p-2">
+ <summary class="tw-p-1">
{{ctx.Locale.Tr "repo.need_auth"}}
</summary>
- <div class="gt-p-2">
+ <div class="tw-p-1">
<div class="inline field {{if .Err_Auth}}error{{end}}">
<label for="mirror_username">{{ctx.Locale.Tr "username"}}</label>
<input id="mirror_username" name="mirror_username" value="{{$address.Username}}" {{if not .mirror_username}}data-need-clear="true"{{end}}>
@@ -262,10 +262,10 @@
<p class="help">{{ctx.Locale.Tr "repo.mirror_address_desc"}}</p>
</div>
<details class="ui optional field" {{if or .Err_PushMirrorAuth .push_mirror_username}}open{{end}}>
- <summary class="gt-p-2">
+ <summary class="tw-p-1">
{{ctx.Locale.Tr "repo.need_auth"}}
</summary>
- <div class="gt-p-2">
+ <div class="tw-p-1">
<div class="inline field {{if .Err_PushMirrorAuth}}error{{end}}">
<label for="push_mirror_username">{{ctx.Locale.Tr "username"}}</label>
<input id="push_mirror_username" name="push_mirror_username" value="{{.push_mirror_username}}">
diff --git a/templates/repo/settings/protected_branch.tmpl b/templates/repo/settings/protected_branch.tmpl
index e1ee7b36f5..a991e68e6f 100644
--- a/templates/repo/settings/protected_branch.tmpl
+++ b/templates/repo/settings/protected_branch.tmpl
@@ -10,17 +10,17 @@
<label>{{ctx.Locale.Tr "repo.settings.protect_branch_name_pattern"}}</label>
<input name="rule_name" type="text" value="{{.Rule.RuleName}}">
<input name="rule_id" type="hidden" value="{{.Rule.ID}}">
- <p class="help gt-ml-0">{{ctx.Locale.Tr "repo.settings.protect_branch_name_pattern_desc"}}</p>
+ <p class="help tw-ml-0">{{ctx.Locale.Tr "repo.settings.protect_branch_name_pattern_desc"}}</p>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.protect_protected_file_patterns"}}</label>
<input name="protected_file_patterns" type="text" value="{{.Rule.ProtectedFilePatterns}}">
- <p class="help gt-ml-0">{{ctx.Locale.Tr "repo.settings.protect_protected_file_patterns_desc"}}</p>
+ <p class="help tw-ml-0">{{ctx.Locale.Tr "repo.settings.protect_protected_file_patterns_desc"}}</p>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.protect_unprotected_file_patterns"}}</label>
<input name="unprotected_file_patterns" type="text" value="{{.Rule.UnprotectedFilePatterns}}">
- <p class="help gt-ml-0">{{ctx.Locale.Tr "repo.settings.protect_unprotected_file_patterns_desc"}}</p>
+ <p class="help tw-ml-0">{{ctx.Locale.Tr "repo.settings.protect_unprotected_file_patterns_desc"}}</p>
</div>
{{.CsrfTokenHtml}}
@@ -98,7 +98,7 @@
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.protect_required_approvals"}}</label>
<input name="required_approvals" type="number" value="{{.Rule.RequiredApprovals}}">
- <p class="help gt-ml-0">{{ctx.Locale.Tr "repo.settings.protect_required_approvals_desc"}}</p>
+ <p class="help tw-ml-0">{{ctx.Locale.Tr "repo.settings.protect_required_approvals_desc"}}</p>
</div>
<div class="grouped fields">
<div class="field">
@@ -179,7 +179,7 @@
<tr>
<td>
<span>{{.}}</span>
- <span class="status-check-matched-mark gt-hidden" data-status-check="{{.}}">{{ctx.Locale.Tr "repo.settings.protect_status_check_matched"}}</span>
+ <span class="status-check-matched-mark tw-hidden" data-status-check="{{.}}">{{ctx.Locale.Tr "repo.settings.protect_status_check_matched"}}</span>
</td>
</tr>
{{else}}
diff --git a/templates/repo/settings/units/issues.tmpl b/templates/repo/settings/units/issues.tmpl
index 77f5782151..b4217ddfb5 100644
--- a/templates/repo/settings/units/issues.tmpl
+++ b/templates/repo/settings/units/issues.tmpl
@@ -20,7 +20,7 @@
<label>{{ctx.Locale.Tr "repo.settings.use_internal_issue_tracker"}}</label>
</div>
</div>
- <div class="field gt-pl-4 {{if (.Repository.UnitEnabled $.Context $.UnitTypeExternalTracker)}}disabled{{end}}" id="internal_issue_box">
+ <div class="field tw-pl-4 {{if (.Repository.UnitEnabled $.Context $.UnitTypeExternalTracker)}}disabled{{end}}" id="internal_issue_box">
{{if .Repository.CanEnableTimetracker}}
<div class="field">
<div class="ui checkbox">
@@ -52,7 +52,7 @@
<label>{{ctx.Locale.Tr "repo.settings.use_external_issue_tracker"}}</label>
</div>
</div>
- <div class="field gt-pl-4 {{if not (.Repository.UnitEnabled $.Context $.UnitTypeExternalTracker)}}disabled{{end}}" id="external_issue_box">
+ <div class="field tw-pl-4 {{if not (.Repository.UnitEnabled $.Context $.UnitTypeExternalTracker)}}disabled{{end}}" id="external_issue_box">
<div class="field">
<label for="external_tracker_url">{{ctx.Locale.Tr "repo.settings.external_tracker_url"}}</label>
<input id="external_tracker_url" name="external_tracker_url" type="url" value="{{(.Repository.MustGetUnit $.Context $.UnitTypeExternalTracker).ExternalTrackerConfig.ExternalTrackerURL}}">
diff --git a/templates/repo/settings/units/wiki.tmpl b/templates/repo/settings/units/wiki.tmpl
index c3be39f1cc..dc83483b04 100644
--- a/templates/repo/settings/units/wiki.tmpl
+++ b/templates/repo/settings/units/wiki.tmpl
@@ -36,7 +36,7 @@
<label>{{ctx.Locale.Tr "repo.settings.use_external_wiki"}}</label>
</div>
</div>
- <div class="field gt-pl-4 {{if not (.Repository.UnitEnabled $.Context $.UnitTypeExternalWiki)}}disabled{{end}}" id="external_wiki_box">
+ <div class="field tw-pl-4 {{if not (.Repository.UnitEnabled $.Context $.UnitTypeExternalWiki)}}disabled{{end}}" id="external_wiki_box">
<label for="external_wiki_url">{{ctx.Locale.Tr "repo.settings.external_wiki_url"}}</label>
<input id="external_wiki_url" name="external_wiki_url" type="url" value="{{(.Repository.MustGetUnit $.Context $.UnitTypeExternalWiki).ExternalWikiConfig.ExternalWikiURL}}">
<p class="help">{{ctx.Locale.Tr "repo.settings.external_wiki_url_desc"}}</p>
diff --git a/templates/repo/settings/webhook/base_list.tmpl b/templates/repo/settings/webhook/base_list.tmpl
index 9abc03e40e..36e75a7eb5 100644
--- a/templates/repo/settings/webhook/base_list.tmpl
+++ b/templates/repo/settings/webhook/base_list.tmpl
@@ -14,12 +14,12 @@
</div>
{{range .Webhooks}}
<div class="item truncated-item-container">
- <span class="text {{if eq .LastStatus 1}}green{{else if eq .LastStatus 2}}red{{else}}grey{{end}} gt-mr-3">{{svg "octicon-dot-fill" 22}}</span>
- <div class="text truncate tw-flex-1 gt-mr-3">
+ <span class="text {{if eq .LastStatus 1}}green{{else if eq .LastStatus 2}}red{{else}}grey{{end}} tw-mr-2">{{svg "octicon-dot-fill" 22}}</span>
+ <div class="text truncate tw-flex-1 tw-mr-2">
<a title="{{.URL}}" href="{{$.BaseLink}}/{{.ID}}">{{.URL}}</a>
</div>
- <a class="muted gt-p-3" href="{{$.BaseLink}}/{{.ID}}">{{svg "octicon-pencil"}}</a>
- <a class="delete-button gt-p-3" data-url="{{$.Link}}/delete" data-id="{{.ID}}">{{svg "octicon-trash"}}</a>
+ <a class="muted tw-p-2" href="{{$.BaseLink}}/{{.ID}}">{{svg "octicon-pencil"}}</a>
+ <a class="delete-button tw-p-2" data-url="{{$.Link}}/delete" data-id="{{.ID}}">{{svg "octicon-trash"}}</a>
</div>
{{end}}
</div>
diff --git a/templates/repo/settings/webhook/history.tmpl b/templates/repo/settings/webhook/history.tmpl
index e2aee13941..8ee1446a16 100644
--- a/templates/repo/settings/webhook/history.tmpl
+++ b/templates/repo/settings/webhook/history.tmpl
@@ -32,7 +32,7 @@
{{TimeSince .Delivered.AsTime ctx.Locale}}
</span>
</div>
- <div class="info gt-hidden" id="info-{{.ID}}">
+ <div class="info tw-hidden" id="info-{{.ID}}">
<div class="ui top attached tabular menu">
<a class="item active" data-tab="request-{{.ID}}">{{ctx.Locale.Tr "repo.settings.webhook.request"}}</a>
<a class="item" data-tab="response-{{.ID}}">
diff --git a/templates/repo/settings/webhook/link_menu.tmpl b/templates/repo/settings/webhook/link_menu.tmpl
index 811e262db6..2edc5af4ac 100644
--- a/templates/repo/settings/webhook/link_menu.tmpl
+++ b/templates/repo/settings/webhook/link_menu.tmpl
@@ -3,52 +3,10 @@
{{$size = .Size}}
{{end}}
<div class="menu">
- <a class="item" href="{{.BaseLinkNew}}/forgejo/new">
- {{template "shared/webhook/icon" (dict "HookType" "forgejo" "Size" $size)}}
- {{ctx.Locale.Tr "repo.settings.web_hook_name_forgejo"}}
- </a>
- <a class="item" href="{{.BaseLinkNew}}/gitea/new">
- {{template "shared/webhook/icon" (dict "HookType" "gitea" "Size" $size)}}
- {{ctx.Locale.Tr "repo.settings.web_hook_name_gitea"}}
- </a>
- <a class="item" href="{{.BaseLinkNew}}/gogs/new">
- {{template "shared/webhook/icon" (dict "HookType" "gogs" "Size" $size)}}
- {{ctx.Locale.Tr "repo.settings.web_hook_name_gogs"}}
- </a>
- <a class="item" href="{{.BaseLinkNew}}/slack/new">
- {{template "shared/webhook/icon" (dict "HookType" "slack" "Size" $size)}}
- {{ctx.Locale.Tr "repo.settings.web_hook_name_slack"}}
- </a>
- <a class="item" href="{{.BaseLinkNew}}/discord/new">
- {{template "shared/webhook/icon" (dict "HookType" "discord" "Size" $size)}}
- {{ctx.Locale.Tr "repo.settings.web_hook_name_discord"}}
- </a>
- <a class="item" href="{{.BaseLinkNew}}/dingtalk/new">
- {{template "shared/webhook/icon" (dict "HookType" "dingtalk" "Size" $size)}}
- {{ctx.Locale.Tr "repo.settings.web_hook_name_dingtalk"}}
- </a>
- <a class="item" href="{{.BaseLinkNew}}/telegram/new">
- {{template "shared/webhook/icon" (dict "HookType" "telegram" "Size" $size)}}
- {{ctx.Locale.Tr "repo.settings.web_hook_name_telegram"}}
- </a>
- <a class="item" href="{{.BaseLinkNew}}/msteams/new">
- {{template "shared/webhook/icon" (dict "HookType" "msteams" "Size" $size)}}
- {{ctx.Locale.Tr "repo.settings.web_hook_name_msteams"}}
- </a>
- <a class="item" href="{{.BaseLinkNew}}/feishu/new">
- {{template "shared/webhook/icon" (dict "HookType" "feishu" "Size" $size)}}
- {{ctx.Locale.Tr "repo.settings.web_hook_name_feishu_or_larksuite"}}
- </a>
- <a class="item" href="{{.BaseLinkNew}}/matrix/new">
- {{template "shared/webhook/icon" (dict "HookType" "matrix" "Size" $size)}}
- {{ctx.Locale.Tr "repo.settings.web_hook_name_matrix"}}
- </a>
- <a class="item" href="{{.BaseLinkNew}}/wechatwork/new">
- {{template "shared/webhook/icon" (dict "HookType" "wechatwork" "Size" $size)}}
- {{ctx.Locale.Tr "repo.settings.web_hook_name_wechatwork"}}
- </a>
- <a class="item" href="{{.BaseLinkNew}}/packagist/new">
- {{template "shared/webhook/icon" (dict "HookType" "packagist" "Size" $size)}}
- {{ctx.Locale.Tr "repo.settings.web_hook_name_packagist"}}
- </a>
+ {{range .WebhookList}}
+ <a class="item" href="{{$.BaseLinkNew}}/{{.Type}}/new">
+ {{.Icon $size}}
+ {{ctx.Locale.Tr (print "repo.settings.web_hook_name_" .Type)}}
+ </a>
+ {{end}}
</div>
diff --git a/templates/repo/settings/webhook/settings.tmpl b/templates/repo/settings/webhook/settings.tmpl
index 2cbbef3e40..0a39643260 100644
--- a/templates/repo/settings/webhook/settings.tmpl
+++ b/templates/repo/settings/webhook/settings.tmpl
@@ -22,7 +22,7 @@
</div>
</div>
- <div class="events fields ui grid {{if not .Webhook.ChooseEvents}}gt-hidden{{end}}">
+ <div class="events fields ui grid {{if not .Webhook.ChooseEvents}}tw-hidden{{end}}">
<!-- Repository Events -->
<div class="fourteen wide column">
<label>{{ctx.Locale.Tr "repo.settings.event_header_repository"}}</label>
diff --git a/templates/repo/sub_menu.tmpl b/templates/repo/sub_menu.tmpl
index 7757abdb71..e43353ef04 100644
--- a/templates/repo/sub_menu.tmpl
+++ b/templates/repo/sub_menu.tmpl
@@ -1,5 +1,5 @@
{{if and (not .HideRepoInfo) (not .IsBlame)}}
-<div class="ui segments repository-summary gt-mt-2 gt-mb-0">
+<div class="ui segments repository-summary tw-mt-1 tw-mb-0">
<div class="ui segment sub-menu repository-menu">
{{if and (.Permission.CanRead $.UnitTypeCode) (not .IsEmptyRepo)}}
<a class="item muted {{if .PageIsCommits}}active{{end}}" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}">
@@ -21,7 +21,7 @@
{{end}}
</div>
{{if and (.Permission.CanRead $.UnitTypeCode) (not .IsEmptyRepo) .LanguageStats}}
- <div class="ui segment sub-menu language-stats-details gt-hidden">
+ <div class="ui segment sub-menu language-stats-details tw-hidden">
{{range .LanguageStats}}
<div class="item">
<i class="color-icon" style="background-color: {{.Color}}"></i>
diff --git a/templates/repo/tag/list.tmpl b/templates/repo/tag/list.tmpl
index 06c02c5f75..82f3dc04a9 100644
--- a/templates/repo/tag/list.tmpl
+++ b/templates/repo/tag/list.tmpl
@@ -6,7 +6,7 @@
{{template "repo/release_tag_header" .}}
<h4 class="ui top attached header">
<div class="five wide column tw-flex tw-items-center">
- {{svg "octicon-tag" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.tags"}}
+ {{svg "octicon-tag" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.tags"}}
</div>
</h4>
{{$canReadReleases := $.Permission.CanRead $.UnitTypeReleases}}
@@ -16,38 +16,39 @@
{{range $idx, $release := .Releases}}
<tr>
<td class="tag">
- <h3 class="release-tag-name gt-mb-3">
+ <h3 class="release-tag-name tw-mb-2 tw-flex">
{{if $canReadReleases}}
<a class="tw-flex tw-items-center" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
{{else}}
<a class="tw-flex tw-items-center" href="{{$.RepoLink}}/src/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
{{end}}
+ {{template "repo/tag/verification_box" (dict "ctxData" $ "release" $release)}}
</h3>
<div class="download tw-flex tw-items-center">
{{if $.Permission.CanRead $.UnitTypeCode}}
{{if .CreatedUnix}}
- <span class="gt-mr-3">{{svg "octicon-clock" 16 "gt-mr-2"}}{{TimeSinceUnix .CreatedUnix ctx.Locale}}</span>
+ <span class="tw-mr-2">{{svg "octicon-clock" 16 "tw-mr-1"}}{{TimeSinceUnix .CreatedUnix ctx.Locale}}</span>
{{end}}
- <a class="gt-mr-3 gt-mono muted" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha .Sha1}}</a>
+ <a class="tw-mr-2 tw-font-mono muted" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha .Sha1}}</a>
{{if not $.DisableDownloadSourceArchives}}
- <a class="archive-link gt-mr-3 muted" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "gt-mr-2"}}ZIP</a>
- <a class="archive-link gt-mr-3 muted" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "gt-mr-2"}}TAR.GZ</a>
+ <a class="archive-link tw-mr-2 muted" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-1"}}ZIP</a>
+ <a class="archive-link tw-mr-2 muted" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-1"}}TAR.GZ</a>
{{end}}
{{if (and $canReadReleases $.CanCreateRelease $release.IsTag)}}
- <a class="gt-mr-3 muted" href="{{$.RepoLink}}/releases/new?tag={{.TagName}}">{{svg "octicon-tag" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.new_release"}}</a>
+ <a class="tw-mr-2 muted" href="{{$.RepoLink}}/releases/new?tag={{.TagName}}">{{svg "octicon-tag" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.new_release"}}</a>
{{end}}
{{if (and ($.Permission.CanWrite $.UnitTypeCode) $release.IsTag)}}
- <a class="ui delete-button gt-mr-3 muted" data-url="{{$.RepoLink}}/tags/delete" data-id="{{.ID}}">
- {{svg "octicon-trash" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.delete_tag"}}
+ <a class="ui delete-button tw-mr-2 muted" data-url="{{$.RepoLink}}/tags/delete" data-id="{{.ID}}">
+ {{svg "octicon-trash" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.delete_tag"}}
</a>
{{end}}
{{if and $canReadReleases (not $release.IsTag)}}
- <a class="gt-mr-3 muted" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{svg "octicon-tag" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.detail"}}</a>
+ <a class="tw-mr-2 muted" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{svg "octicon-tag" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.detail"}}</a>
{{end}}
{{end}}
</div>
diff --git a/templates/repo/tag/verification_box.tmpl b/templates/repo/tag/verification_box.tmpl
new file mode 100644
index 0000000000..3cf88ac23e
--- /dev/null
+++ b/templates/repo/tag/verification_box.tmpl
@@ -0,0 +1,27 @@
+{{$v := call .ctxData.VerifyTag .release}}
+{{if call .ctxData.HasSignature $v}}
+ {{$class := "isSigned"}}
+ {{$href := ""}}
+ {{if $v.Verified}}
+ {{$href = $v.SigningUser.HomeLink}}
+ {{$class = (print $class " isVerified")}}
+ {{else}}
+ {{$class = (print $class " isWarning")}}
+ {{end}}
+
+ <a {{if $href}}href="{{$href}}"{{end}} class="ui label tw-ml-2 {{$class}}">
+ {{if $v.Verified}}
+ <div title="{{$v.Reason}}">
+ {{if ne $v.SigningUser.ID 0}}
+ {{svg "gitea-lock"}}
+ {{ctx.AvatarUtils.Avatar $v.SigningUser 28 "signature"}}
+ {{else}}
+ <span title="{{ctx.Locale.Tr "gpg.default_key"}}">{{svg "gitea-lock-cog"}}</span>
+ {{ctx.AvatarUtils.AvatarByEmail $v.Verification.SigningEmail "" 28 "signature"}}
+ {{end}}
+ </div>
+ {{else}}
+ <span title="{{ctx.Locale.Tr $v.Reason}}">{{svg "gitea-unlock"}}</span>
+ {{end}}
+ </a>
+{{end}}
diff --git a/templates/repo/tag/verification_line.tmpl b/templates/repo/tag/verification_line.tmpl
new file mode 100644
index 0000000000..f83719de23
--- /dev/null
+++ b/templates/repo/tag/verification_line.tmpl
@@ -0,0 +1,80 @@
+{{$v := call .ctxData.VerifyTag .release}}
+{{if call .ctxData.HasSignature $v}}
+ {{$class := "isSigned"}}
+ {{$href := ""}}
+ {{if $v.Verified}}
+ {{$href = $v.SigningUser.HomeLink}}
+ {{$class = (print $class " isVerified")}}
+ {{else}}
+ {{$class = (print $class " isWarning")}}
+ {{end}}
+
+ <div class="ui bottom attached message tw-text-left tw-flex tw-content-center tw-justify-between tag-signature-row tw-flex-wrap tw-mb-0 {{$class}}">
+ <div class="tw-flex tw-content-center">
+ {{if $v.Verified}}
+ {{if ne $v.SigningUser.ID 0}}
+ {{svg "gitea-lock" 16 "tw-mr-2"}}
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by"}}</span>
+ {{ctx.AvatarUtils.Avatar $v.SigningUser 28 "tw-mr-2"}}
+ <a href="{{$v.SigningUser.HomeLink}}"><strong>{{$v.SigningUser.GetDisplayName}}</strong></a>
+ {{else}}
+ <span title="{{ctx.Locale.Tr "gpg.default_key"}}">{{svg "gitea-lock-cog" 16 "tw-mr-2"}}</span>
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by"}}:</span>
+ {{ctx.AvatarUtils.AvatarByEmail $v.SigningEmail "" 28 "tw-mr-2"}}
+ <strong>{{$v.SigningUser.GetDisplayName}}</strong>
+ {{end}}
+ {{else}}
+ {{svg "gitea-unlock" 16 "tw-mr-2"}}
+ <span class="ui text">{{ctx.Locale.Tr $v.Reason}}</span>
+ {{end}}
+ </div>
+
+ <div class="tw-flex tw-content-center">
+ {{if $v.Verified}}
+ {{if ne $v.SigningUser.ID 0}}
+ {{svg "octicon-verified" 16 "tw-mr-2"}}
+ {{if $v.SigningSSHKey}}
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
+ {{$v.SigningSSHKey.Fingerprint}}
+ {{else}}
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
+ {{$v.SigningKey.PaddedKeyID}}
+ {{end}}
+ {{else}}
+ {{svg "octicon-unverified" 16 "tw-mr-2"}}
+ {{if $v.SigningSSHKey}}
+ <span class="ui text tw-mr-2" data-tooltip-content="{{ctx.Locale.Tr "gpg.default_key"}}">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
+ {{$v.SigningSSHKey.Fingerprint}}
+ {{else}}
+ <span class="ui text tw-mr-2" data-tooltip-content="{{ctx.Locale.Tr "gpg.default_key"}}">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
+ {{$v.SigningKey.PaddedKeyID}}
+ {{end}}
+ {{end}}
+ {{else if $v.Warning}}
+ {{svg "octicon-unverified" 16 "tw-mr-2"}}
+ {{if $v.SigningSSHKey}}
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
+ {{$v.SigningSSHKey.Fingerprint}}
+ {{else}}
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
+ {{$v.SigningKey.PaddedKeyID}}
+ {{end}}
+ {{else}}
+ {{if $v.SigningKey}}
+ {{if ne $v.SigningKey.KeyID ""}}
+ {{svg "octicon-verified" 16 "tw-mr-2"}}
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
+ {{$v.SigningKey.PaddedKeyID}}
+ {{end}}
+ {{end}}
+ {{if $v.SigningSSHKey}}
+ {{if ne $v.SigningSSHKey.Fingerprint ""}}
+ {{svg "octicon-verified" 16 "tw-mr-2"}}
+ <span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
+ {{$v.SigningSSHKey.Fingerprint}}
+ {{end}}
+ {{end}}
+ {{end}}
+ </div>
+ </div>
+{{end}}
diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl
index cbc3bdf675..2680b374cd 100644
--- a/templates/repo/view_file.tmpl
+++ b/templates/repo/view_file.tmpl
@@ -11,7 +11,7 @@
{{end}}
{{if not .ReadmeInList}}
- <div id="repo-file-commit-box" class="ui top attached header list-header gt-mb-4">
+ <div id="repo-file-commit-box" class="ui top attached header list-header tw-mb-4">
<div>
{{template "repo/latest_commit" .}}
</div>
@@ -26,9 +26,9 @@
{{end}}
<h4 class="file-header ui top attached header tw-flex tw-items-center tw-justify-between tw-flex-wrap">
- <div class="file-header-left tw-flex tw-items-center gt-py-3 gt-pr-4">
+ <div class="file-header-left tw-flex tw-items-center tw-py-2 tw-pr-4">
{{if .ReadmeInList}}
- {{svg "octicon-book" 16 "gt-mr-3"}}
+ {{svg "octicon-book" 16 "tw-mr-2"}}
<strong><a class="default-link muted" href="#readme">{{.FileName}}</a></strong>
{{else}}
{{template "repo/file_info" .}}
@@ -42,7 +42,7 @@
</div>
{{end}}
{{if not .ReadmeInList}}
- <div class="ui buttons gt-mr-2">
+ <div class="ui buttons tw-mr-1">
{{if .SymlinkURL}}
<a class="ui mini basic button" href="{{$.SymlinkURL}}" data-kind="follow-symlink">{{ctx.Locale.Tr "repo.file_follow"}}</a>
{{end}}
@@ -55,7 +55,7 @@
{{end}}
<a class="ui mini basic button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{PathEscapeSegments .TreePath}}">{{ctx.Locale.Tr "repo.file_history"}}</a>
{{if .EscapeStatus.Escaped}}
- <button class="ui mini basic button unescape-button gt-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
+ <button class="ui mini basic button unescape-button tw-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
<button class="ui mini basic button escape-button">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
{{end}}
</div>
@@ -85,8 +85,8 @@
{{end}}
{{end}}
{{else if .EscapeStatus.Escaped}}
- <button class="ui mini basic button unescape-button gt-mr-2 gt-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
- <button class="ui mini basic button escape-button gt-mr-2">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
+ <button class="ui mini basic button unescape-button tw-mr-1 tw-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
+ <button class="ui mini basic button escape-button tw-mr-1">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
{{end}}
{{if and .ReadmeInList .CanEditReadmeFile}}
<a class="btn-octicon" data-tooltip-content="{{ctx.Locale.Tr "repo.editor.edit_this_file"}}" href="{{.RepoLink}}/_edit/{{PathEscapeSegments .BranchName}}/{{PathEscapeSegments .TreePath}}/{{PathEscapeSegments .FileName}}">{{svg "octicon-pencil"}}</a>
diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl
index 988a5ddd50..7c463c50a6 100644
--- a/templates/repo/view_list.tmpl
+++ b/templates/repo/view_list.tmpl
@@ -1,4 +1,4 @@
-<table id="repo-files-table" class="ui single line table gt-mt-0" {{if .HasFilesWithoutLatestCommit}}hx-indicator="tr.notready td.message span" hx-trigger="load" hx-swap="morph" hx-post="{{.LastCommitLoaderURL}}"{{end}}>
+<table id="repo-files-table" class="ui single line table tw-mt-0" {{if .HasFilesWithoutLatestCommit}}hx-indicator="tr.notready td.message span" hx-trigger="load" hx-swap="morph" hx-post="{{.LastCommitLoaderURL}}"{{end}}>
<thead>
<tr class="commit-list">
<th colspan="2">
diff --git a/templates/repo/wiki/new.tmpl b/templates/repo/wiki/new.tmpl
index 0e929f24eb..2f51a5fe2e 100644
--- a/templates/repo/wiki/new.tmpl
+++ b/templates/repo/wiki/new.tmpl
@@ -31,7 +31,7 @@
"TextareaContent" $content
)}}
- <div class="field gt-mt-4">
+ <div class="field tw-mt-4">
<input name="message" aria-label="{{ctx.Locale.Tr "repo.wiki.default_commit_message"}}" placeholder="{{ctx.Locale.Tr "repo.wiki.default_commit_message"}}">
</div>
<div class="divider"></div>
diff --git a/templates/repo/wiki/revision.tmpl b/templates/repo/wiki/revision.tmpl
index 182635e011..8e0060d4b3 100644
--- a/templates/repo/wiki/revision.tmpl
+++ b/templates/repo/wiki/revision.tmpl
@@ -22,7 +22,7 @@
</div>
</div>
<h2 class="ui top header">{{ctx.Locale.Tr "repo.wiki.wiki_page_revisions"}}</h2>
- <div class="gt-mt-4">
+ <div class="tw-mt-4">
<h4 class="ui top attached header">
<div class="ui stackable grid">
<div class="sixteen wide column">
diff --git a/templates/repo/wiki/start.tmpl b/templates/repo/wiki/start.tmpl
index dca7a074aa..1b3c3d538a 100644
--- a/templates/repo/wiki/start.tmpl
+++ b/templates/repo/wiki/start.tmpl
@@ -2,7 +2,7 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository wiki start">
{{template "repo/header" .}}
<div class="ui container">
- <div class="ui center segment gt-py-5">
+ <div class="ui center segment tw-py-8">
{{svg "octicon-book" 48}}
<h2>{{ctx.Locale.Tr "repo.wiki.welcome"}}</h2>
<p>{{ctx.Locale.Tr "repo.wiki.welcome_desc"}}</p>
diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl
index ebd7f25130..feb7192bb1 100644
--- a/templates/repo/wiki/view.tmpl
+++ b/templates/repo/wiki/view.tmpl
@@ -45,7 +45,7 @@
</div>
<div class="eight wide right aligned column">
{{if .EscapeStatus.Escaped}}
- <a class="ui small button unescape-button gt-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a>
+ <a class="ui small button unescape-button tw-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a>
<a class="ui small button escape-button">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a>
{{end}}
{{if and .CanWriteWiki (not .Repository.IsMirror)}}
diff --git a/templates/shared/actions/runner_edit.tmpl b/templates/shared/actions/runner_edit.tmpl
index f8bbf23b62..d60f10b71f 100644
--- a/templates/shared/actions/runner_edit.tmpl
+++ b/templates/shared/actions/runner_edit.tmpl
@@ -7,15 +7,15 @@
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<div class="runner-basic-info">
- <div class="field tw-inline-block gt-mr-4">
+ <div class="field tw-inline-block tw-mr-4">
<label>{{ctx.Locale.Tr "actions.runners.status"}}</label>
<span class="ui {{if .Runner.IsOnline}}green{{else}}basic{{end}} label">{{.Runner.StatusLocaleName ctx.Locale}}</span>
</div>
- <div class="field tw-inline-block gt-mr-4">
+ <div class="field tw-inline-block tw-mr-4">
<label>{{ctx.Locale.Tr "actions.runners.last_online"}}</label>
<span>{{if .Runner.LastOnline}}{{TimeSinceUnix .Runner.LastOnline ctx.Locale}}{{else}}{{ctx.Locale.Tr "never"}}{{end}}</span>
</div>
- <div class="field tw-inline-block gt-mr-4">
+ <div class="field tw-inline-block tw-mr-4">
<label>{{ctx.Locale.Tr "actions.runners.labels"}}</label>
<span>
{{range .Runner.AgentLabels}}
@@ -23,7 +23,7 @@
{{end}}
</span>
</div>
- <div class="field tw-inline-block gt-mr-4">
+ <div class="field tw-inline-block tw-mr-4">
<label>{{ctx.Locale.Tr "actions.runners.owner_type"}}</label>
<span data-tooltip-content="{{.Runner.BelongsToOwnerName}}">{{.Runner.BelongsToOwnerType.LocaleString ctx.Locale}}</span>
</div>
diff --git a/templates/shared/combomarkdowneditor.tmpl b/templates/shared/combomarkdowneditor.tmpl
index 64a1aea392..f4d51f510b 100644
--- a/templates/shared/combomarkdowneditor.tmpl
+++ b/templates/shared/combomarkdowneditor.tmpl
@@ -49,7 +49,7 @@ Template Attributes:
</text-expander>
<script>
if (localStorage?.getItem('markdown-editor-monospace') === 'true') {
- document.querySelector('.markdown-text-editor').classList.add('gt-mono');
+ document.querySelector('.markdown-text-editor').classList.add('tw-font-mono');
}
</script>
</div>
diff --git a/templates/shared/issuelist.tmpl b/templates/shared/issuelist.tmpl
index 639221abb9..06887c2914 100644
--- a/templates/shared/issuelist.tmpl
+++ b/templates/shared/issuelist.tmpl
@@ -5,7 +5,7 @@
<div class="flex-item-icon">
{{if $.CanWriteIssuesOrPulls}}
- <input type="checkbox" autocomplete="off" class="issue-checkbox gt-mr-4" data-issue-id={{.ID}} aria-label="{{ctx.Locale.Tr "repo.issues.action_check"}} &quot;{{.Title}}&quot;">
+ <input type="checkbox" autocomplete="off" class="issue-checkbox tw-mr-4" data-issue-id={{.ID}} aria-label="{{ctx.Locale.Tr "repo.issues.action_check"}} &quot;{{.Title}}&quot;">
{{end}}
{{template "shared/issueicon" .}}
</div>
@@ -19,7 +19,7 @@
{{template "repo/commit_statuses" dict "Status" (index $.CommitLastStatus .PullRequest.ID) "Statuses" (index $.CommitStatuses .PullRequest.ID)}}
{{end}}
{{end}}
- <span class="labels-list gt-ml-2">
+ <span class="labels-list tw-ml-1">
{{range .Labels}}
<a href="?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{RenderLabel $.Context .}}</a>
{{end}}
diff --git a/templates/shared/repo_search.tmpl b/templates/shared/repo_search.tmpl
index d2fe2de143..d08d6017ed 100644
--- a/templates/shared/repo_search.tmpl
+++ b/templates/shared/repo_search.tmpl
@@ -37,7 +37,7 @@
</div>
</div>
<!-- Sort -->
- <div class="ui small dropdown type jump item gt-mr-0">
+ <div class="ui small dropdown type jump item tw-mr-0">
<span class="text">
{{ctx.Locale.Tr "repo.issues.filter_sort"}}
</span>
diff --git a/templates/shared/search/code/results.tmpl b/templates/shared/search/code/results.tmpl
index a22820e788..022192a150 100644
--- a/templates/shared/search/code/results.tmpl
+++ b/templates/shared/search/code/results.tmpl
@@ -1,8 +1,8 @@
<div class="flex-text-block tw-flex-wrap">
{{range $term := .SearchResultLanguages}}
- <a class="ui {{if eq $.Language $term.Language}}primary{{end}} basic label gt-m-0"
+ <a class="ui {{if eq $.Language $term.Language}}primary{{end}} basic label tw-m-0"
href="?q={{$.Keyword}}{{if ne $.Language $term.Language}}&l={{$term.Language}}{{end}}&fuzzy={{$.IsFuzzy}}">
- <i class="color-icon gt-mr-3" style="background-color: {{$term.Color}}"></i>
+ <i class="color-icon tw-mr-2" style="background-color: {{$term.Color}}"></i>
{{$term.Language}}
<div class="detail">{{$term.Count}}</div>
</a>
@@ -12,7 +12,7 @@
{{range $result := .SearchResults}}
{{$repo := or $.Repo (index $.RepoMaps .RepoID)}}
<div class="diff-file-box diff-box file-content non-diff-file-content repo-search-result">
- <h4 class="ui top attached normal header tw-flex tw-flex-wrap">
+ <h4 class="ui top attached header tw-font-normal tw-flex tw-flex-wrap">
{{if not $.Repo}}
<span class="file tw-flex-1">
<a rel="nofollow" href="{{$repo.Link}}">{{$repo.FullName}}</a>
diff --git a/templates/shared/searchbottom.tmpl b/templates/shared/searchbottom.tmpl
index b22324585c..bee0397259 100644
--- a/templates/shared/searchbottom.tmpl
+++ b/templates/shared/searchbottom.tmpl
@@ -1,11 +1,11 @@
{{if or .result.Language (not .result.UpdatedUnix.IsZero)}}
<div class="ui bottom attached table segment tw-flex tw-items-center tw-justify-between">
- <div class="tw-flex tw-items-center gt-ml-4">
+ <div class="tw-flex tw-items-center tw-ml-4">
{{if .result.Language}}
- <i class="color-icon gt-mr-3" style="background-color: {{.result.Color}}"></i>{{.result.Language}}
+ <i class="color-icon tw-mr-2" style="background-color: {{.result.Color}}"></i>{{.result.Language}}
{{end}}
</div>
- <div class="gt-mr-4">
+ <div class="tw-mr-4">
{{if not .result.UpdatedUnix.IsZero}}
<span class="ui grey text">{{ctx.Locale.Tr "explore.code_last_indexed_at" (TimeSinceUnix .result.UpdatedUnix ctx.Locale)}}</span>
{{end}}
diff --git a/templates/shared/secrets/add_list.tmpl b/templates/shared/secrets/add_list.tmpl
index c943a1944d..ea59459083 100644
--- a/templates/shared/secrets/add_list.tmpl
+++ b/templates/shared/secrets/add_list.tmpl
@@ -30,7 +30,7 @@
<span class="color-text-light-2">
{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}}
</span>
- <button class="ui btn interact-bg link-action gt-p-3"
+ <button class="ui btn interact-bg link-action tw-p-2"
data-url="{{$.Link}}/delete?id={{.ID}}"
data-modal-confirm="{{ctx.Locale.Tr "secrets.deletion.description"}}"
data-tooltip-content="{{ctx.Locale.Tr "secrets.deletion"}}"
diff --git a/templates/shared/user/authorlink.tmpl b/templates/shared/user/authorlink.tmpl
index 4d8ad736be..d57a635b4b 100644
--- a/templates/shared/user/authorlink.tmpl
+++ b/templates/shared/user/authorlink.tmpl
@@ -1 +1 @@
-<a class="author text black tw-font-semibold muted"{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>{{.GetDisplayName}}</a>{{if .IsBot}}<span class="ui basic label gt-p-2">bot</span>{{end}}
+<a class="author text black tw-font-semibold muted"{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>{{.GetDisplayName}}</a>{{if .IsBot}}<span class="ui basic label tw-p-1">bot</span>{{end}}
diff --git a/templates/shared/user/blocked_users.tmpl b/templates/shared/user/blocked_users.tmpl
new file mode 100644
index 0000000000..e83a039ef5
--- /dev/null
+++ b/templates/shared/user/blocked_users.tmpl
@@ -0,0 +1,83 @@
+<h4 class="ui top attached header">
+ {{ctx.Locale.Tr "user.block.title"}}
+</h4>
+<div class="ui attached segment">
+ <p>{{ctx.Locale.Tr "user.block.info_1"}}</p>
+ <ul>
+ <li>{{ctx.Locale.Tr "user.block.info_2"}}</li>
+ <li>{{ctx.Locale.Tr "user.block.info_3"}}</li>
+ <li>{{ctx.Locale.Tr "user.block.info_4"}}</li>
+ <li>{{ctx.Locale.Tr "user.block.info_5"}}</li>
+ <li>{{ctx.Locale.Tr "user.block.info_6"}}</li>
+ <li>{{ctx.Locale.Tr "user.block.info_7"}}</li>
+ </ul>
+</div>
+<div class="ui segment">
+ <form class="ui form ignore-dirty" action="{{$.Link}}" method="post">
+ {{.CsrfTokenHtml}}
+ <input type="hidden" name="action" value="block" />
+ <div id="search-user-box" class="field ui fluid search input">
+ <input class="prompt tw-mr-2" name="blockee" placeholder="{{ctx.Locale.Tr "search.user_kind"}}" autocomplete="off" required>
+ <button class="ui red button">{{ctx.Locale.Tr "user.block.block"}}</button>
+ </div>
+ <div class="field">
+ <label>{{ctx.Locale.Tr "user.block.note.title"}}</label>
+ <input name="note">
+ <p class="help">{{ctx.Locale.Tr "user.block.note.info"}}</p>
+ </div>
+ </form>
+</div>
+<h4 class="ui top attached header">
+ {{ctx.Locale.Tr "user.block.list"}}
+</h4>
+<div class="ui attached segment">
+ <div class="flex-list">
+ {{range .UserBlocks}}
+ <div class="flex-item">
+ <div class="flex-item-leading">
+ {{ctx.AvatarUtils.Avatar .Blockee}}
+ </div>
+ <div class="flex-item-main">
+ <div class="flex-item-title">
+ <a class="item" href="{{.Blockee.HTMLURL}}">{{.Blockee.GetDisplayName}}</a>
+ </div>
+ {{if .Note}}
+ <div class="flex-item-body">
+ <i>{{ctx.Locale.Tr "user.block.note"}}:</i> {{.Note}}
+ </div>
+ {{end}}
+ </div>
+ <div class="flex-item-trailing">
+ <button class="ui compact mini button show-modal" data-modal="#block-user-note-modal" data-modal-modal-blockee="{{.Blockee.Name}}" data-modal-modal-note="{{.Note}}">{{ctx.Locale.Tr "user.block.note.edit"}}</button>
+ <form action="{{$.Link}}" method="post">
+ {{$.CsrfTokenHtml}}
+ <input type="hidden" name="action" value="unblock" />
+ <input type="hidden" name="blockee" value="{{.Blockee.Name}}" />
+ <button class="ui compact mini button">{{ctx.Locale.Tr "user.block.unblock"}}</button>
+ </form>
+ </div>
+ </div>
+ {{else}}
+ <div class="item">{{ctx.Locale.Tr "user.block.list.none"}}</div>
+ {{end}}
+ </div>
+</div>
+<div class="ui small modal" id="block-user-note-modal">
+ <div class="header">{{ctx.Locale.Tr "user.block.note.edit"}}</div>
+ <div class="content">
+ <form class="ui form" action="{{$.Link}}" method="post">
+ {{.CsrfTokenHtml}}
+ <input type="hidden" name="action" value="note" />
+ <input type="hidden" name="blockee" class="modal-blockee" />
+ <div class="field">
+ <label>{{ctx.Locale.Tr "user.block.note.title"}}</label>
+ <input name="note" class="modal-note" />
+ <p class="help">{{ctx.Locale.Tr "user.block.note.info"}}</p>
+ </div>
+ <div class="text right actions">
+ <button class="ui cancel button">{{ctx.Locale.Tr "cancel"}}</button>
+ <button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
+ </div>
+ </form>
+ </div>
+</div>
diff --git a/templates/shared/user/profile_big_avatar.tmpl b/templates/shared/user/profile_big_avatar.tmpl
index b1c837d36c..bc7785629e 100644
--- a/templates/shared/user/profile_big_avatar.tmpl
+++ b/templates/shared/user/profile_big_avatar.tmpl
@@ -18,10 +18,10 @@
{{svg "octicon-gear" 18}}
</a>
{{end}}</span>
- <div class="gt-mt-3">
- <a class="muted" href="{{.ContextUser.HomeLink}}?tab=followers">{{svg "octicon-person" 18 "gt-mr-2"}}{{.NumFollowers}} {{ctx.Locale.Tr "user.followers"}}</a> · <a class="muted" href="{{.ContextUser.HomeLink}}?tab=following">{{.NumFollowing}} {{ctx.Locale.Tr "user.following"}}</a>
+ <div class="tw-mt-2">
+ <a class="muted" href="{{.ContextUser.HomeLink}}?tab=followers">{{svg "octicon-person" 18 "tw-mr-1"}}{{.NumFollowers}} {{ctx.Locale.Tr "user.followers"}}</a> · <a class="muted" href="{{.ContextUser.HomeLink}}?tab=following">{{.NumFollowing}} {{ctx.Locale.Tr "user.following"}}</a>
{{if .EnableFeed}}
- <a href="{{.ContextUser.HomeLink}}.rss"><i class="ui text grey gt-ml-3" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">{{svg "octicon-rss" 18}}</i></a>
+ <a href="{{.ContextUser.HomeLink}}.rss"><i class="ui text grey tw-ml-2" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">{{svg "octicon-rss" 18}}</i></a>
{{end}}
</div>
</div>
diff --git a/templates/shared/variables/variable_list.tmpl b/templates/shared/variables/variable_list.tmpl
index fc2ac98e29..06c71c0610 100644
--- a/templates/shared/variables/variable_list.tmpl
+++ b/templates/shared/variables/variable_list.tmpl
@@ -32,7 +32,7 @@
<span class="color-text-light-2">
{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}}
</span>
- <button class="btn interact-bg gt-p-3 show-modal"
+ <button class="btn interact-bg tw-p-2 show-modal"
data-tooltip-content="{{ctx.Locale.Tr "actions.variables.edit"}}"
data-modal="#edit-variable-modal"
data-modal-form.action="{{$.Link}}/{{.ID}}/edit"
@@ -42,7 +42,7 @@
>
{{svg "octicon-pencil"}}
</button>
- <button class="btn interact-bg gt-p-3 link-action"
+ <button class="btn interact-bg tw-p-2 link-action"
data-tooltip-content="{{ctx.Locale.Tr "actions.variables.deletion"}}"
data-url="{{$.Link}}/{{.ID}}/delete"
data-modal-confirm="{{ctx.Locale.Tr "actions.variables.deletion.description"}}"
diff --git a/templates/status/500.tmpl b/templates/status/500.tmpl
index a75c769167..8e250abe27 100644
--- a/templates/status/500.tmpl
+++ b/templates/status/500.tmpl
@@ -28,23 +28,23 @@
</div>
</div>
</nav>
- <div class="divider gt-my-0"></div>
+ <div class="divider tw-my-0"></div>
<div role="main" class="page-content status-page-500">
<div class="ui container" >
<style> .ui.message.flash-message { text-align: left; } </style>
{{template "base/alert" .}}
</div>
<div class="ui container center">
- <h1 class="gt-mt-5 error-code">500</h1>
+ <h1 class="tw-mt-8 error-code">500</h1>
<p>{{ctx.Locale.Tr "error.server_internal"}}</p>
</div>
<div class="divider"></div>
- <div class="ui container gt-my-5">
+ <div class="ui container tw-my-8">
{{if .ErrorMsg}}
<p>{{ctx.Locale.Tr "error.occurred"}}:</p>
<pre class="tw-whitespace-pre-wrap tw-break-all">{{.ErrorMsg}}</pre>
{{end}}
- <div class="center gt-mt-5">
+ <div class="center tw-mt-8">
{{if or .SignedUser.IsAdmin .ShowFooterVersion}}<p>{{ctx.Locale.Tr "admin.config.app_ver"}}: {{AppVer}}</p>{{end}}
{{if .SignedUser.IsAdmin}}<p>{{ctx.Locale.Tr "error.report_message"}}</p>{{end}}
</div>
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index ee1ed64eaf..181c564f4a 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -11422,6 +11422,9 @@
"204": {
"$ref": "#/responses/empty"
},
+ "403": {
+ "$ref": "#/responses/forbidden"
+ },
"404": {
"$ref": "#/responses/notFound"
},
diff --git a/templates/user/auth/link_account.tmpl b/templates/user/auth/link_account.tmpl
index 81ea92c959..8dd49ccd60 100644
--- a/templates/user/auth/link_account.tmpl
+++ b/templates/user/auth/link_account.tmpl
@@ -1,6 +1,6 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content user link-account">
- <overflow-menu class="ui secondary pointing tabular top attached borderless menu navbar tw-bg-header-wrapper">
+ <overflow-menu class="ui secondary pointing tabular top attached borderless menu secondary-nav">
<div class="overflow-menu-items tw-justify-center">
<!-- TODO handle .ShowRegistrationButton once other login bugs are fixed -->
{{if not .AllowOnlyInternalRegistration}}
diff --git a/templates/user/auth/signin_inner.tmpl b/templates/user/auth/signin_inner.tmpl
index ad86b0b881..9872096fbc 100644
--- a/templates/user/auth/signin_inner.tmpl
+++ b/templates/user/auth/signin_inner.tmpl
@@ -53,11 +53,11 @@
<div class="divider divider-text">
{{ctx.Locale.Tr "sign_in_or"}}
</div>
- <div id="oauth2-login-navigator" class="gt-py-2">
+ <div id="oauth2-login-navigator" class="tw-py-1">
<div class="tw-flex tw-flex-col tw-justify-center">
<div id="oauth2-login-navigator-inner" class="tw-flex tw-flex-col tw-flex-wrap tw-items-center tw-gap-2">
{{range $provider := .OAuth2Providers}}
- <a class="{{$provider.Name}} ui button tw-flex tw-items-center tw-justify-center gt-py-3 tw-w-full oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}">
+ <a class="{{$provider.Name}} ui button tw-flex tw-items-center tw-justify-center tw-py-2 tw-w-full oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}">
{{$provider.IconHTML 28}}
{{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}}
</a>
diff --git a/templates/user/auth/signin_navbar.tmpl b/templates/user/auth/signin_navbar.tmpl
index 0c6fbd2a43..01b994b982 100644
--- a/templates/user/auth/signin_navbar.tmpl
+++ b/templates/user/auth/signin_navbar.tmpl
@@ -1,5 +1,5 @@
{{if or .EnableOpenIDSignIn .EnableSSPI}}
-<overflow-menu class="ui secondary pointing tabular top attached borderless menu navbar tw-bg-header-wrapper">
+<overflow-menu class="ui secondary pointing tabular top attached borderless menu navbar secondary-nav">
<div class="overflow-menu-items tw-justify-center">
<a class="{{if .PageIsLogin}}active {{end}}item" rel="nofollow" href="{{AppSubUrl}}/user/login">
{{ctx.Locale.Tr "auth.tab_signin"}}
diff --git a/templates/user/auth/signup_inner.tmpl b/templates/user/auth/signup_inner.tmpl
index 26d9091b68..bdb691d833 100644
--- a/templates/user/auth/signup_inner.tmpl
+++ b/templates/user/auth/signup_inner.tmpl
@@ -58,11 +58,11 @@
<div class="divider divider-text">
{{ctx.Locale.Tr "sign_in_or"}}
</div>
- <div id="oauth2-login-navigator" class="gt-py-2">
+ <div id="oauth2-login-navigator" class="tw-py-1">
<div class="tw-flex tw-flex-col tw-justify-center">
<div id="oauth2-login-navigator-inner" class="tw-flex tw-flex-col tw-flex-wrap tw-items-center tw-gap-2">
{{range $provider := .OAuth2Providers}}
- <a class="{{$provider.Name}} ui button tw-flex tw-items-center tw-justify-center gt-py-3 tw-w-full oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}">
+ <a class="{{$provider.Name}} ui button tw-flex tw-items-center tw-justify-center tw-py-2 tw-w-full oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}">
{{$provider.IconHTML 28}}
{{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}}
</a>
diff --git a/templates/user/auth/signup_openid_navbar.tmpl b/templates/user/auth/signup_openid_navbar.tmpl
index 9cf81b048f..89068ddde1 100644
--- a/templates/user/auth/signup_openid_navbar.tmpl
+++ b/templates/user/auth/signup_openid_navbar.tmpl
@@ -1,4 +1,4 @@
-<overflow-menu class="ui secondary pointing tabular top attached borderless menu navbar tw-bg-header-wrapper">
+<overflow-menu class="ui secondary pointing tabular top attached borderless menu secondary-nav">
<div class="overflow-menu-items tw-justify-center">
<a class="{{if .PageIsOpenIDConnect}}active {{end}}item" href="{{AppSubUrl}}/user/openid/connect">
{{ctx.Locale.Tr "auth.openid_connect_title"}}
diff --git a/templates/user/auth/webauthn.tmpl b/templates/user/auth/webauthn.tmpl
index 24dd75eed5..1b84765323 100644
--- a/templates/user/auth/webauthn.tmpl
+++ b/templates/user/auth/webauthn.tmpl
@@ -10,7 +10,7 @@
{{template "base/alert" .}}
<p>{{ctx.Locale.Tr "webauthn_sign_in"}}</p>
</div>
- <div class="ui attached segment tw-flex tw-items-center tw-justify-center tw-gap-1 gt-py-3">
+ <div class="ui attached segment tw-flex tw-items-center tw-justify-center tw-gap-1 tw-py-2">
<div class="is-loading tw-w-[40px] tw-h-[40px]"></div>
{{ctx.Locale.Tr "webauthn_press_button"}}
</div>
diff --git a/templates/user/auth/webauthn_error.tmpl b/templates/user/auth/webauthn_error.tmpl
index fc6064db76..511ff7c287 100644
--- a/templates/user/auth/webauthn_error.tmpl
+++ b/templates/user/auth/webauthn_error.tmpl
@@ -1,7 +1,7 @@
-<div id="webauthn-error" class="ui negative message gt-hidden">
+<div id="webauthn-error" class="ui negative message tw-hidden">
<div class="header">{{ctx.Locale.Tr "webauthn_error"}}</div>
- <div id="webauthn-error-msg" class="gt-pt-3"></div>
- <div class="gt-hidden">
+ <div id="webauthn-error-msg" class="tw-pt-2"></div>
+ <div class="tw-hidden">
<div data-webauthn-error-msg="browser">{{ctx.Locale.Tr "webauthn_unsupported_browser"}}</div>
<div data-webauthn-error-msg="unknown">{{ctx.Locale.Tr "webauthn_error_unknown"}}</div>
<div data-webauthn-error-msg="insecure">{{ctx.Locale.Tr "webauthn_error_insecure"}}</div>
diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl
index fbe151607c..60aa194534 100644
--- a/templates/user/dashboard/feeds.tmpl
+++ b/templates/user/dashboard/feeds.tmpl
@@ -119,7 +119,7 @@
{{end}}
</div>
<div class="flex-item-trailing">
- {{svg (printf "octicon-%s" (ActionIcon .GetOpType)) 32 "text grey gt-mr-2"}}
+ {{svg (printf "octicon-%s" (ActionIcon .GetOpType)) 32 "text grey tw-mr-1"}}
</div>
</div>
{{end}}
diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl
index 5080821dd1..89f23163f7 100644
--- a/templates/user/dashboard/issues.tmpl
+++ b/templates/user/dashboard/issues.tmpl
@@ -38,11 +38,11 @@
<div class="list-header">
<div class="small-menu-items ui compact tiny menu list-header-toggle">
<a class="item{{if not .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=open&q={{$.Keyword}}">
- {{svg "octicon-issue-opened" 16 "gt-mr-3"}}
+ {{svg "octicon-issue-opened" 16 "tw-mr-2"}}
{{ctx.Locale.PrettyNumber .IssueStats.OpenCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.open_title"}}
</a>
<a class="item{{if .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=closed&q={{$.Keyword}}">
- {{svg "octicon-issue-closed" 16 "gt-mr-3"}}
+ {{svg "octicon-issue-closed" 16 "tw-mr-2"}}
{{ctx.Locale.PrettyNumber .IssueStats.ClosedCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.closed_title"}}
</a>
</div>
@@ -52,7 +52,7 @@
<input type="hidden" name="sort" value="{{$.SortType}}">
<input type="hidden" name="state" value="{{$.State}}">
{{template "shared/search/input" dict "Value" $.Keyword}}
- <button id="issue-list-quick-goto" class="ui small icon button gt-hidden" data-tooltip-content="{{ctx.Locale.Tr "explore.go_to"}}">{{svg "octicon-hash"}}</button>
+ <button id="issue-list-quick-goto" class="ui small icon button tw-hidden" data-tooltip-content="{{ctx.Locale.Tr "explore.go_to"}}">{{svg "octicon-hash"}}</button>
{{template "shared/search/button"}}
</div>
</form>
diff --git a/templates/user/dashboard/milestones.tmpl b/templates/user/dashboard/milestones.tmpl
index 3a260c3d10..0f1e866a21 100644
--- a/templates/user/dashboard/milestones.tmpl
+++ b/templates/user/dashboard/milestones.tmpl
@@ -37,11 +37,11 @@
<div class="list-header">
<div class="small-menu-items ui compact tiny menu list-header-toggle">
<a class="item{{if not .IsShowClosed}} active{{end}}" href="?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=open&q={{$.Keyword}}">
- {{svg "octicon-milestone" 16 "gt-mr-3"}}
+ {{svg "octicon-milestone" 16 "tw-mr-2"}}
{{ctx.Locale.PrettyNumber .MilestoneStats.OpenCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.open_title"}}
</a>
<a class="item{{if .IsShowClosed}} active{{end}}" href="?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=closed&q={{$.Keyword}}">
- {{svg "octicon-check" 16 "gt-mr-3"}}
+ {{svg "octicon-check" 16 "tw-mr-2"}}
{{ctx.Locale.PrettyNumber .MilestoneStats.ClosedCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.closed_title"}}
</a>
</div>
@@ -72,7 +72,7 @@
{{range .Milestones}}
<li class="milestone-card">
<div class="milestone-header">
- <h3 class="flex-text-block gt-m-0">
+ <h3 class="flex-text-block tw-m-0">
<span class="ui large label">
{{.Repo.FullName}}
</span>
@@ -80,7 +80,7 @@
<a class="muted" href="{{.Repo.Link}}/milestone/{{.ID}}">{{.Name}}</a>
</h3>
<div class="tw-flex tw-items-center">
- <span class="gt-mr-3">{{.Completeness}}%</span>
+ <span class="tw-mr-2">{{.Completeness}}%</span>
<progress value="{{.Completeness}}" max="100"></progress>
</div>
</div>
diff --git a/templates/user/dashboard/navbar.tmpl b/templates/user/dashboard/navbar.tmpl
index 480c39e8bf..b2ee198b0a 100644
--- a/templates/user/dashboard/navbar.tmpl
+++ b/templates/user/dashboard/navbar.tmpl
@@ -1,4 +1,4 @@
-<div class="dashboard-navbar">
+<div class="secondary-nav tw-border-b tw-border-b-secondary">
<div class="ui secondary stackable menu">
<div class="item">
<div class="ui floating dropdown jump">
@@ -35,11 +35,6 @@
</a>
{{end}}
</div>
- {{if .SignedUser.CanCreateOrganization}}
- <a class="item" href="{{AppSubUrl}}/org/create">
- {{svg "octicon-plus"}}&nbsp;&nbsp;&nbsp;{{ctx.Locale.Tr "new_org"}}
- </a>
- {{end}}
</div>
</div>
</div>
@@ -105,4 +100,3 @@
{{end}}
</div>
</div>
-<div class="divider tw-mt-0"></div>
diff --git a/templates/user/notification/notification_div.tmpl b/templates/user/notification/notification_div.tmpl
index f0fe6ac6f4..04e79ba749 100644
--- a/templates/user/notification/notification_div.tmpl
+++ b/templates/user/notification/notification_div.tmpl
@@ -1,11 +1,11 @@
<div role="main" aria-label="{{.Title}}" class="page-content user notification" id="notification_div" data-sequence-number="{{.SequenceNumber}}">
<div class="ui container">
{{$notificationUnreadCount := call .NotificationUnreadCount}}
- <div class="tw-flex tw-items-center tw-justify-between gt-mb-4">
+ <div class="tw-flex tw-items-center tw-justify-between tw-mb-4">
<div class="small-menu-items ui compact tiny menu">
<a class="{{if eq .Status 1}}active {{end}}item" href="{{AppSubUrl}}/notifications?q=unread">
{{ctx.Locale.Tr "notification.unread"}}
- <div class="notifications-unread-count ui label {{if not $notificationUnreadCount}}gt-hidden{{end}}">{{$notificationUnreadCount}}</div>
+ <div class="notifications-unread-count ui label {{if not $notificationUnreadCount}}tw-hidden{{end}}">{{$notificationUnreadCount}}</div>
</a>
<a class="{{if eq .Status 2}}active {{end}}item" href="{{AppSubUrl}}/notifications?q=read">
{{ctx.Locale.Tr "notification.read"}}
@@ -14,19 +14,19 @@
{{if and (eq .Status 1)}}
<form action="{{AppSubUrl}}/notifications/purge" method="post">
{{$.CsrfTokenHtml}}
- <div class="{{if not $notificationUnreadCount}}gt-hidden{{end}}">
- <button class="ui mini button primary gt-mr-0" title="{{ctx.Locale.Tr "notification.mark_all_as_read"}}">
+ <div class="{{if not $notificationUnreadCount}}tw-hidden{{end}}">
+ <button class="ui mini button primary tw-mr-0" title="{{ctx.Locale.Tr "notification.mark_all_as_read"}}">
{{svg "octicon-checklist"}}
</button>
</div>
</form>
{{end}}
</div>
- <div class="gt-p-0">
+ <div class="tw-p-0">
<div id="notification_table">
{{if not .Notifications}}
- <div class="tw-flex tw-items-center tw-flex-col gt-p-4">
- {{svg "octicon-inbox" 56 "gt-mb-4"}}
+ <div class="tw-flex tw-items-center tw-flex-col tw-p-4">
+ {{svg "octicon-inbox" 56 "tw-mb-4"}}
{{if eq .Status 1}}
{{ctx.Locale.Tr "notification.no_unread"}}
{{else}}
@@ -35,8 +35,8 @@
</div>
{{else}}
{{range $notification := .Notifications}}
- <div class="notifications-item tw-flex tw-items-center tw-flex-wrap tw-gap-2 gt-p-3" id="notification_{{.ID}}" data-status="{{.Status}}">
- <div class="notifications-icon gt-ml-3 gt-mr-2 tw-self-start gt-mt-2">
+ <div class="notifications-item tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-p-2" id="notification_{{.ID}}" data-status="{{.Status}}">
+ <div class="notifications-icon tw-ml-2 tw-mr-1 tw-self-start tw-mt-1">
{{if .Issue}}
{{template "shared/issueicon" .Issue}}
{{else}}
@@ -47,10 +47,10 @@
<div class="notifications-top-row tw-text-13">
{{.Repository.FullName}} {{if .Issue}}<span class="text light-3">#{{.Issue.Index}}</span>{{end}}
{{if eq .Status 3}}
- {{svg "octicon-pin" 13 "text blue gt-mt-1 gt-ml-2"}}
+ {{svg "octicon-pin" 13 "text blue tw-mt-0.5 tw-ml-1"}}
{{end}}
</div>
- <div class="notifications-bottom-row tw-text-16 gt-py-1">
+ <div class="notifications-bottom-row tw-text-16 tw-py-0.5">
<span class="issue-title">
{{if .Issue}}
{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}
@@ -60,20 +60,20 @@
</span>
</div>
</a>
- <div class="notifications-updated tw-items-center gt-mr-3">
+ <div class="notifications-updated tw-items-center tw-mr-2">
{{if .Issue}}
{{TimeSinceUnix .Issue.UpdatedUnix ctx.Locale}}
{{else}}
{{TimeSinceUnix .UpdatedUnix ctx.Locale}}
{{end}}
</div>
- <div class="notifications-buttons tw-items-center tw-justify-end tw-gap-1 gt-px-2">
+ <div class="notifications-buttons tw-items-center tw-justify-end tw-gap-1 tw-px-1">
{{if ne .Status 3}}
<form action="{{AppSubUrl}}/notifications/status" method="post">
{{$.CsrfTokenHtml}}
<input type="hidden" name="notification_id" value="{{.ID}}">
<input type="hidden" name="status" value="pinned">
- <button class="btn interact-bg gt-p-3" title="{{ctx.Locale.Tr "notification.pin"}}"
+ <button class="btn interact-bg tw-p-2" title="{{ctx.Locale.Tr "notification.pin"}}"
data-url="{{AppSubUrl}}/notifications/status"
data-status="pinned"
data-page="{{$.Page.Paginater.Current}}"
@@ -89,7 +89,7 @@
<input type="hidden" name="notification_id" value="{{.ID}}">
<input type="hidden" name="status" value="read">
<input type="hidden" name="page" value="{{$.Page.Paginater.Current}}">
- <button class="btn interact-bg gt-p-3" title="{{ctx.Locale.Tr "notification.mark_as_read"}}"
+ <button class="btn interact-bg tw-p-2" title="{{ctx.Locale.Tr "notification.mark_as_read"}}"
data-url="{{AppSubUrl}}/notifications/status"
data-status="read"
data-page="{{$.Page.Paginater.Current}}"
@@ -104,7 +104,7 @@
<input type="hidden" name="notification_id" value="{{.ID}}">
<input type="hidden" name="status" value="unread">
<input type="hidden" name="page" value="{{$.Page.Paginater.Current}}">
- <button class="btn interact-bg gt-p-3" title="{{ctx.Locale.Tr "notification.mark_as_unread"}}"
+ <button class="btn interact-bg tw-p-2" title="{{ctx.Locale.Tr "notification.mark_as_unread"}}"
data-url="{{AppSubUrl}}/notifications/status"
data-status="unread"
data-page="{{$.Page.Paginater.Current}}"
diff --git a/templates/user/notification/notification_subscriptions.tmpl b/templates/user/notification/notification_subscriptions.tmpl
index eb71621d92..a5a965ca52 100644
--- a/templates/user/notification/notification_subscriptions.tmpl
+++ b/templates/user/notification/notification_subscriptions.tmpl
@@ -18,11 +18,11 @@
{{ctx.Locale.Tr "all"}}
</a>
<a class="{{if eq .State "open"}}active {{end}}item" href="?sort={{$.SortType}}&state=open&issueType={{$.IssueType}}&labels={{$.Labels}}">
- {{svg "octicon-issue-opened" 16 "gt-mr-3"}}
+ {{svg "octicon-issue-opened" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.issues.open_title"}}
</a>
<a class="{{if eq .State "closed"}}active {{end}}item" href="?sort={{$.SortType}}&state=closed&issueType={{$.IssueType}}&labels={{$.Labels}}">
- {{svg "octicon-issue-closed" 16 "gt-mr-3"}}
+ {{svg "octicon-issue-closed" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.issues.closed_title"}}
</a>
</div>
diff --git a/templates/user/overview/package_versions.tmpl b/templates/user/overview/package_versions.tmpl
index f6f963aecb..b2cc814e13 100644
--- a/templates/user/overview/package_versions.tmpl
+++ b/templates/user/overview/package_versions.tmpl
@@ -14,7 +14,7 @@
{{template "shared/user/profile_big_avatar" .}}
</div>
<div class="ui twelve wide column">
- <div class="gt-mb-4">
+ <div class="tw-mb-4">
{{template "user/overview/header" .}}
</div>
{{template "package/shared/versionlist" .}}
diff --git a/templates/user/overview/packages.tmpl b/templates/user/overview/packages.tmpl
index 30ff871cb2..95cb506e57 100644
--- a/templates/user/overview/packages.tmpl
+++ b/templates/user/overview/packages.tmpl
@@ -14,7 +14,7 @@
{{template "shared/user/profile_big_avatar" .}}
</div>
<div class="ui twelve wide column">
- <div class="gt-mb-4">
+ <div class="tw-mb-4">
{{template "user/overview/header" .}}
</div>
{{template "package/shared/list" .}}
diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl
index da68d50ab9..5880c80cc9 100644
--- a/templates/user/profile.tmpl
+++ b/templates/user/profile.tmpl
@@ -7,7 +7,7 @@
{{template "shared/user/profile_big_avatar" .}}
</div>
<div class="ui twelve wide column">
- <div class="gt-mb-4">
+ <div class="tw-mb-4">
{{template "user/overview/header" .}}
</div>
diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl
index 63f490991a..a97136f407 100644
--- a/templates/user/settings/account.tmpl
+++ b/templates/user/settings/account.tmpl
@@ -42,7 +42,7 @@
<div class="ui list">
{{if $.EnableNotifyMail}}
<div class="item">
- <div class="gt-mb-3">{{ctx.Locale.Tr "settings.email_desc"}}</div>
+ <div class="tw-mb-2">{{ctx.Locale.Tr "settings.email_desc"}}</div>
<form action="{{AppSubUrl}}/user/settings/account/email" class="ui form" method="post">
{{$.CsrfTokenHtml}}
<input name="_method" type="hidden" value="NOTIFICATION">
@@ -96,7 +96,7 @@
</form>
</div>
{{end}}
- <div class="content gt-py-3">
+ <div class="content tw-py-2">
<strong>{{.Email}}</strong>
{{if .IsPrimary}}
<div class="ui primary label">{{ctx.Locale.Tr "settings.primary"}}</div>
diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl
index e43cf2ebbe..dc58df221e 100644
--- a/templates/user/settings/applications.tmpl
+++ b/templates/user/settings/applications.tmpl
@@ -18,7 +18,7 @@
<div class="flex-item-main">
<details>
<summary><span class="flex-item-title">{{.Name}}</span></summary>
- <p class="gt-my-2">
+ <p class="tw-my-1">
{{ctx.Locale.Tr "settings.repo_and_org_access"}}:
{{if .DisplayPublicOnly}}
{{ctx.Locale.Tr "settings.permissions_public_only"}}
@@ -26,8 +26,8 @@
{{ctx.Locale.Tr "settings.permissions_access_all"}}
{{end}}
</p>
- <p class="gt-my-2">{{ctx.Locale.Tr "settings.permissions_list"}}</p>
- <ul class="gt-my-2">
+ <p class="tw-my-1">{{ctx.Locale.Tr "settings.permissions_list"}}</p>
+ <ul class="tw-my-1">
{{range .Scope.StringSlice}}
{{if (ne . $.AccessTokenScopePublicOnly)}}
<li>{{.}}</li>
@@ -41,7 +41,7 @@
</div>
<div class="flex-item-trailing">
<button class="ui red tiny button delete-button" data-modal-id="delete-token" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
- {{svg "octicon-trash" 16 "gt-mr-2"}}
+ {{svg "octicon-trash" 16 "tw-mr-1"}}
{{ctx.Locale.Tr "settings.delete_token"}}
</button>
</div>
@@ -62,20 +62,20 @@
<div class="field">
<label>{{ctx.Locale.Tr "settings.repo_and_org_access"}}</label>
<label class="tw-cursor-pointer">
- <input class="enable-system gt-mt-2 gt-mr-2" type="radio" name="scope" value="{{$.AccessTokenScopePublicOnly}}">
+ <input class="enable-system tw-mt-1 tw-mr-1" type="radio" name="scope" value="{{$.AccessTokenScopePublicOnly}}">
{{ctx.Locale.Tr "settings.permissions_public_only"}}
</label>
<label class="tw-cursor-pointer">
- <input class="enable-system gt-mt-2 gt-mr-2" type="radio" name="scope" value="" checked>
+ <input class="enable-system tw-mt-1 tw-mr-1" type="radio" name="scope" value="" checked>
{{ctx.Locale.Tr "settings.permissions_access_all"}}
</label>
</div>
<details class="ui optional field">
- <summary class="gt-pb-4 gt-pl-2">
+ <summary class="tw-pb-4 tw-pl-1">
{{ctx.Locale.Tr "settings.select_permissions"}}
</summary>
<p class="activity meta">
- <i>{{ctx.Locale.Tr "settings.access_token_desc" (`href="/api/swagger" target="_blank"`|SafeHTML) (`href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`|SafeHTML)}}</i>
+ <i>{{ctx.Locale.Tr "settings.access_token_desc" (`href="/api/swagger" target="_blank"`|SafeHTML) (`href="https://forgejo.org/docs/latest/user/token-scope/" target="_blank"`|SafeHTML)}}</i>
</p>
<div class="scoped-access-token-mount">
<scoped-access-token-selector
@@ -90,7 +90,7 @@
{{ctx.Locale.Tr "settings.generate_token"}}
</button>
</form>{{/* Fomantic ".ui.form .warning.message" is hidden by default, so put the warning message out of the form*/}}
- <div id="scoped-access-warning" class="ui warning message center gt-hidden">
+ <div id="scoped-access-warning" class="ui warning message center tw-hidden">
{{ctx.Locale.Tr "settings.at_least_one_permission"}}
</div>
</div>
diff --git a/templates/user/settings/applications_oauth2_edit_form.tmpl b/templates/user/settings/applications_oauth2_edit_form.tmpl
index c0bddd55b3..f7ef115693 100644
--- a/templates/user/settings/applications_oauth2_edit_form.tmpl
+++ b/templates/user/settings/applications_oauth2_edit_form.tmpl
@@ -26,7 +26,7 @@
<form class="ui form ignore-dirty" action="{{.FormActionPath}}/regenerate_secret" method="post">
{{.CsrfTokenHtml}}
{{ctx.Locale.Tr "settings.oauth2_regenerate_secret_hint"}}
- <button class="ui mini button gt-ml-3" type="submit">{{ctx.Locale.Tr "settings.oauth2_regenerate_secret"}}</button>
+ <button class="ui mini button tw-ml-2" type="submit">{{ctx.Locale.Tr "settings.oauth2_regenerate_secret"}}</button>
</form>
</div>
</div>
diff --git a/templates/user/settings/applications_oauth2_list.tmpl b/templates/user/settings/applications_oauth2_list.tmpl
index bfbebb104d..cfcb6d053d 100644
--- a/templates/user/settings/applications_oauth2_list.tmpl
+++ b/templates/user/settings/applications_oauth2_list.tmpl
@@ -21,12 +21,12 @@
<span class="ui basic label" data-tooltip-content="{{ctx.Locale.Tr "settings.oauth2_application_locked"}}">{{ctx.Locale.Tr "locked"}}</span>
{{else}}
<a href="{{$.Link}}/oauth2/{{.ID}}" class="ui primary tiny button">
- {{svg "octicon-pencil" 16 "gt-mr-2"}}
+ {{svg "octicon-pencil" 16 "tw-mr-1"}}
{{ctx.Locale.Tr "settings.oauth2_application_edit"}}
</a>
<button class="ui red tiny button delete-button" data-modal-id="remove-gitea-oauth2-application"
data-url="{{$.Link}}/oauth2/{{.ID}}/delete">
- {{svg "octicon-trash" 16 "gt-mr-2"}}
+ {{svg "octicon-trash" 16 "tw-mr-1"}}
{{ctx.Locale.Tr "settings.delete_key"}}
</button>
{{end}}
diff --git a/templates/user/settings/grants_oauth2.tmpl b/templates/user/settings/grants_oauth2.tmpl
index 92fea1306f..b5ae3e0337 100644
--- a/templates/user/settings/grants_oauth2.tmpl
+++ b/templates/user/settings/grants_oauth2.tmpl
@@ -29,7 +29,7 @@
<div class="ui g-modal-confirm delete modal" id="revoke-gitea-oauth2-grant">
<div class="header">
- {{svg "octicon-shield" 16 "gt-mr-2"}}
+ {{svg "octicon-shield" 16 "tw-mr-1"}}
{{ctx.Locale.Tr "settings.revoke_oauth2_grant"}}
</div>
<div class="content">
diff --git a/templates/user/settings/keys_gpg.tmpl b/templates/user/settings/keys_gpg.tmpl
index b6bae89512..386de7e2f8 100644
--- a/templates/user/settings/keys_gpg.tmpl
+++ b/templates/user/settings/keys_gpg.tmpl
@@ -5,7 +5,7 @@
</div>
</h4>
<div class="ui attached segment">
- <div class="{{if not .HasGPGError}}gt-hidden{{end}} gt-mb-4" id="add-gpg-key-panel">
+ <div class="{{if not .HasGPGError}}tw-hidden{{end}} tw-mb-4" id="add-gpg-key-panel">
<form class="ui form{{if .HasGPGError}} error{{end}}" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="title" value="none">
diff --git a/templates/user/settings/keys_principal.tmpl b/templates/user/settings/keys_principal.tmpl
index b6acb63c5e..37d8fb0e95 100644
--- a/templates/user/settings/keys_principal.tmpl
+++ b/templates/user/settings/keys_principal.tmpl
@@ -36,7 +36,7 @@
</div>
<br>
- <div {{if not .HasPrincipalError}}class="gt-hidden"{{end}} id="add-ssh-principal-panel">
+ <div {{if not .HasPrincipalError}}class="tw-hidden"{{end}} id="add-ssh-principal-panel">
<h4 class="ui top attached header">
{{ctx.Locale.Tr "settings.add_new_principal"}}
</h4>
diff --git a/templates/user/settings/keys_ssh.tmpl b/templates/user/settings/keys_ssh.tmpl
index 5c14d81bef..3b6e92f7d7 100644
--- a/templates/user/settings/keys_ssh.tmpl
+++ b/templates/user/settings/keys_ssh.tmpl
@@ -7,7 +7,7 @@
</div>
</h4>
<div class="ui attached segment">
- <div class="{{if not .HasSSHError}}gt-hidden{{end}} gt-mb-4" id="add-ssh-key-panel">
+ <div class="{{if not .HasSSHError}}tw-hidden{{end}} tw-mb-4" id="add-ssh-key-panel">
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="field {{if .Err_Title}}error{{end}}">
diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl
index d1c68656b6..aaaf8f30db 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -9,8 +9,8 @@
{{.CsrfTokenHtml}}
<div class="required field {{if .Err_Name}}error{{end}}">
<label for="username">{{ctx.Locale.Tr "username"}}
- <span class="text red gt-hidden" id="name-change-prompt"> {{ctx.Locale.Tr "settings.change_username_prompt"}}</span>
- <span class="text red gt-hidden" id="name-change-redirect-prompt"> {{ctx.Locale.Tr "settings.change_username_redirect_prompt"}}</span>
+ <span class="text red tw-hidden" id="name-change-prompt"> {{ctx.Locale.Tr "settings.change_username_prompt"}}</span>
+ <span class="text red tw-hidden" id="name-change-redirect-prompt"> {{ctx.Locale.Tr "settings.change_username_redirect_prompt"}}</span>
</label>
<input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if or (not .SignedUser.IsLocal) .IsReverseProxy}}disabled{{end}} maxlength="40">
{{if or (not .SignedUser.IsLocal) .IsReverseProxy}}
@@ -106,7 +106,7 @@
<label>{{ctx.Locale.Tr "settings.lookup_avatar_by_mail"}}</label>
</div>
</div>
- <div class="field gt-pl-4 {{if .Err_Gravatar}}error{{end}}">
+ <div class="field tw-pl-4 {{if .Err_Gravatar}}error{{end}}">
<label for="gravatar">Avatar {{ctx.Locale.Tr "email"}}</label>
<input id="gravatar" name="gravatar" value="{{.SignedUser.AvatarEmail}}">
</div>
@@ -119,7 +119,7 @@
</div>
</div>
- <div class="inline field gt-pl-4">
+ <div class="inline field tw-pl-4">
<label for="new-avatar">{{ctx.Locale.Tr "settings.choose_new_avatar"}}</label>
<input id="new-avatar" name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp">
</div>
diff --git a/templates/user/settings/repos.tmpl b/templates/user/settings/repos.tmpl
index 41cdae2968..c874ccd878 100644
--- a/templates/user/settings/repos.tmpl
+++ b/templates/user/settings/repos.tmpl
@@ -9,7 +9,7 @@
<div class="ui middle aligned divided list">
{{range $dirI, $dir := .Dirs}}
{{$repo := index $.ReposMap $dir}}
- <div class="item {{if not $repo}}gt-py-2{{end}}">{{/* if not repo, then there are "adapt" buttons, so the padding shouldn't be that default large*/}}
+ <div class="item {{if not $repo}}tw-py-1{{end}}">{{/* if not repo, then there are "adapt" buttons, so the padding shouldn't be that default large*/}}
<div class="content">
{{if $repo}}
{{if $repo.IsPrivate}}
@@ -30,11 +30,11 @@
<span><a href="{{$repo.BaseRepo.Link}}">{{$repo.BaseRepo.OwnerName}}/{{$repo.BaseRepo.Name}}</a></span>
{{end}}
{{else}}
- <span class="icon tw-inline-block gt-pt-3">{{svg "octicon-file-directory-fill"}}</span>
- <span class="name tw-inline-block gt-pt-3">{{$.ContextUser.Name}}/{{$dir}}</span>
+ <span class="icon tw-inline-block tw-pt-2">{{svg "octicon-file-directory-fill"}}</span>
+ <span class="name tw-inline-block tw-pt-2">{{$.ContextUser.Name}}/{{$dir}}</span>
<div class="tw-float-right">
{{if $.allowAdopt}}
- <button class="ui button primary show-modal gt-p-3" data-modal="#adopt-unadopted-modal-{{$dirI}}"><span class="icon">{{svg "octicon-plus"}}</span><span class="label">{{ctx.Locale.Tr "repo.adopt_preexisting_label"}}</span></button>
+ <button class="ui button primary show-modal tw-p-2" data-modal="#adopt-unadopted-modal-{{$dirI}}"><span class="icon">{{svg "octicon-plus"}}</span><span class="label">{{ctx.Locale.Tr "repo.adopt_preexisting_label"}}</span></button>
<div class="ui g-modal-confirm modal" id="adopt-unadopted-modal-{{$dirI}}">
<div class="header">
<span class="label">{{ctx.Locale.Tr "repo.adopt_preexisting"}}</span>
@@ -51,7 +51,7 @@
</div>
{{end}}
{{if $.allowDelete}}
- <button class="ui button red show-modal gt-p-3" data-modal="#delete-unadopted-modal-{{$dirI}}"><span class="icon">{{svg "octicon-x"}}</span><span class="label">{{ctx.Locale.Tr "repo.delete_preexisting_label"}}</span></button>
+ <button class="ui button red show-modal tw-p-2" data-modal="#delete-unadopted-modal-{{$dirI}}"><span class="icon">{{svg "octicon-x"}}</span><span class="label">{{ctx.Locale.Tr "repo.delete_preexisting_label"}}</span></button>
<div class="ui g-modal-confirm modal" id="delete-unadopted-modal-{{$dirI}}">
<div class="header">
<span class="label">{{ctx.Locale.Tr "repo.delete_preexisting"}}</span>
@@ -86,15 +86,15 @@
<div class="item">
<div class="content">
{{if .IsPrivate}}
- {{svg "octicon-lock" 16 "gt-mr-2 iconFloat text gold"}}
+ {{svg "octicon-lock" 16 "tw-mr-1 iconFloat text gold"}}
{{else if .IsFork}}
- {{svg "octicon-repo-forked" 16 "gt-mr-2 iconFloat"}}
+ {{svg "octicon-repo-forked" 16 "tw-mr-1 iconFloat"}}
{{else if .IsMirror}}
- {{svg "octicon-mirror" 16 "gt-mr-2 iconFloat"}}
+ {{svg "octicon-mirror" 16 "tw-mr-1 iconFloat"}}
{{else if .IsTemplate}}
- {{svg "octicon-repo-template" 16 "gt-mr-2 iconFloat"}}
+ {{svg "octicon-repo-template" 16 "tw-mr-1 iconFloat"}}
{{else}}
- {{svg "octicon-repo" 16 "gt-mr-2 iconFloat"}}
+ {{svg "octicon-repo" 16 "tw-mr-1 iconFloat"}}
{{end}}
<a class="name" href="{{.Link}}">{{.OwnerName}}/{{.Name}}</a>
<span>{{FileSize .Size}}</span>
diff --git a/templates/webhook/new.tmpl b/templates/webhook/new.tmpl
index 17eb578b0e..8afdb1fa5d 100644
--- a/templates/webhook/new.tmpl
+++ b/templates/webhook/new.tmpl
@@ -2,7 +2,7 @@
{{.CustomHeaderTitle}}
<div class="ui right type dropdown">
<div class="text tw-flex tw-items-center">
- {{template "shared/webhook/icon" (dict "Size" 20 "HookType" .ctxData.HookType)}}
+ {{.ctxData.WebhookHandler.Icon 20}}
{{ctx.Locale.Tr (print "repo.settings.web_hook_name_" .ctxData.HookType)}}
</div>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
diff --git a/templates/webhook/new/feishu.tmpl b/templates/webhook/new/feishu.tmpl
index b691c3f18c..ead11770de 100644
--- a/templates/webhook/new/feishu.tmpl
+++ b/templates/webhook/new/feishu.tmpl
@@ -1,5 +1,5 @@
-<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://feishu.cn" (ctx.Locale.Tr "repo.settings.web_hook_name_feishu")}}</p>
-<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://larksuite.com" (ctx.Locale.Tr "repo.settings.web_hook_name_larksuite")}}</p>
+<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://feishu.cn" (ctx.Locale.Tr "repo.settings.web_hook_name_feishu_only")}}</p>
+<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://larksuite.com" (ctx.Locale.Tr "repo.settings.web_hook_name_larksuite_only")}}</p>
<form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "feishu/new"}}" method="post">
{{.CsrfTokenHtml}}
<div class="required field {{if .Err_PayloadURL}}error{{end}}">
diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go
index 12681ab0f9..304dac0f45 100644
--- a/tests/e2e/e2e_test.go
+++ b/tests/e2e/e2e_test.go
@@ -77,7 +77,7 @@ func TestMain(m *testing.M) {
// TestE2e should be the only test e2e necessary. It will collect all "*.test.e2e.js" files in this directory and build a test for each.
func TestE2e(t *testing.T) {
- // Find the paths of all e2e test files in test test directory.
+ // Find the paths of all e2e test files in test directory.
searchGlob := filepath.Join(filepath.Dir(setting.AppPath), "tests", "e2e", "*.test.e2e.js")
paths, err := filepath.Glob(searchGlob)
if err != nil {
diff --git a/tests/e2e/example.test.e2e.js b/tests/e2e/example.test.e2e.js
index c741663a38..57c69a2917 100644
--- a/tests/e2e/example.test.e2e.js
+++ b/tests/e2e/example.test.e2e.js
@@ -23,7 +23,7 @@ test('Test Register Form', async ({page}, workerInfo) => {
await page.click('form button.ui.primary.button:visible');
// Make sure we routed to the home page. Else login failed.
await expect(page.url()).toBe(`${workerInfo.project.use.baseURL}/`);
- await expect(page.locator('.dashboard-navbar span>img.ui.avatar')).toBeVisible();
+ await expect(page.locator('.secondary-nav span>img.ui.avatar')).toBeVisible();
await expect(page.locator('.ui.positive.message.flash-success')).toHaveText('Account was successfully created. Welcome!');
save_visual(page);
diff --git a/tests/e2e/utils_e2e.js b/tests/e2e/utils_e2e.js
index fba13ab426..d60c78b16e 100644
--- a/tests/e2e/utils_e2e.js
+++ b/tests/e2e/utils_e2e.js
@@ -52,7 +52,7 @@ export async function save_visual(page) {
fullPage: true,
timeout: 20000,
mask: [
- page.locator('.dashboard-navbar span>img.ui.avatar'),
+ page.locator('.secondary-nav span>img.ui.avatar'),
page.locator('.ui.dropdown.jump.item span>img.ui.avatar'),
],
});
diff --git a/tests/integration/api_packages_generic_test.go b/tests/integration/api_packages_generic_test.go
index 93525ac4b1..1cbae599af 100644
--- a/tests/integration/api_packages_generic_test.go
+++ b/tests/integration/api_packages_generic_test.go
@@ -84,7 +84,7 @@ func TestPackageGeneric(t *testing.T) {
t.Run("InvalidParameter", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
- req := NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, "invalid+package name", packageVersion, filename), bytes.NewReader(content)).
+ req := NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, "invalid package name", packageVersion, filename), bytes.NewReader(content)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusBadRequest)
@@ -92,7 +92,7 @@ func TestPackageGeneric(t *testing.T) {
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusBadRequest)
- req = NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, "inval+id.na me"), bytes.NewReader(content)).
+ req = NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, "inva|id.name"), bytes.NewReader(content)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusBadRequest)
})
diff --git a/tests/integration/cmd_forgejo_actions_test.go b/tests/integration/cmd_forgejo_actions_test.go
index 44211007f5..e45526ac7a 100644
--- a/tests/integration/cmd_forgejo_actions_test.go
+++ b/tests/integration/cmd_forgejo_actions_test.go
@@ -4,8 +4,11 @@ package integration
import (
gocontext "context"
+ "errors"
+ "io"
"net/url"
"os"
+ "os/exec"
"strings"
"testing"
@@ -19,16 +22,18 @@ import (
func Test_CmdForgejo_Actions(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
- token, err := cmdForgejoCaptureOutput(t, []string{"forgejo", "forgejo-cli", "actions", "generate-runner-token"})
+ token, err := runMainApp("forgejo-cli", "actions", "generate-runner-token")
assert.NoError(t, err)
assert.EqualValues(t, 40, len(token))
- secret, err := cmdForgejoCaptureOutput(t, []string{"forgejo", "forgejo-cli", "actions", "generate-secret"})
+ secret, err := runMainApp("forgejo-cli", "actions", "generate-secret")
assert.NoError(t, err)
assert.EqualValues(t, 40, len(secret))
- _, err = cmdForgejoCaptureOutput(t, []string{"forgejo", "forgejo-cli", "actions", "register"})
- assert.ErrorContains(t, err, "at least one of the --secret")
+ _, err = runMainApp("forgejo-cli", "actions", "register")
+ var exitErr *exec.ExitError
+ assert.True(t, errors.As(err, &exitErr))
+ assert.Contains(t, string(exitErr.Stderr), "at least one of the --secret")
for _, testCase := range []struct {
testName string
@@ -62,10 +67,12 @@ func Test_CmdForgejo_Actions(t *testing.T) {
},
} {
t.Run(testCase.testName, func(t *testing.T) {
- cmd := []string{"forgejo", "forgejo-cli", "actions", "register", "--secret", testCase.secret, "--scope", testCase.scope}
- output, err := cmdForgejoCaptureOutput(t, cmd)
- assert.ErrorContains(t, err, testCase.errorMessage)
+ output, err := runMainApp("forgejo-cli", "actions", "register", "--secret", testCase.secret, "--scope", testCase.scope)
assert.EqualValues(t, "", output)
+
+ var exitErr *exec.ExitError
+ assert.True(t, errors.As(err, &exitErr))
+ assert.Contains(t, string(exitErr.Stderr), testCase.errorMessage)
})
}
@@ -75,7 +82,7 @@ func Test_CmdForgejo_Actions(t *testing.T) {
for _, testCase := range []struct {
testName string
secretOption func() string
- stdin []string
+ stdin io.Reader
}{
{
testName: "secret from argument",
@@ -88,7 +95,7 @@ func Test_CmdForgejo_Actions(t *testing.T) {
secretOption: func() string {
return "--secret-stdin"
},
- stdin: []string{secret},
+ stdin: strings.NewReader(secret),
},
{
testName: "secret from file",
@@ -100,8 +107,7 @@ func Test_CmdForgejo_Actions(t *testing.T) {
},
} {
t.Run(testCase.testName, func(t *testing.T) {
- cmd := []string{"forgejo", "forgejo-cli", "actions", "register", testCase.secretOption(), "--scope=org26"}
- uuid, err := cmdForgejoCaptureOutput(t, cmd, testCase.stdin...)
+ uuid, err := runMainAppWithStdin(testCase.stdin, "forgejo-cli", "actions", "register", testCase.secretOption(), "--scope=org26")
assert.NoError(t, err)
assert.EqualValues(t, expecteduuid, uuid)
})
@@ -161,7 +167,7 @@ func Test_CmdForgejo_Actions(t *testing.T) {
} {
t.Run(testCase.testName, func(t *testing.T) {
cmd := []string{
- "forgejo", "forgejo-cli", "actions", "register",
+ "actions", "register",
"--secret", testCase.secret, "--scope", testCase.scope,
}
if testCase.name != "" {
@@ -177,7 +183,7 @@ func Test_CmdForgejo_Actions(t *testing.T) {
// Run twice to verify it is idempotent
//
for i := 0; i < 2; i++ {
- uuid, err := cmdForgejoCaptureOutput(t, cmd)
+ uuid, err := runMainApp("forgejo-cli", cmd...)
assert.NoError(t, err)
if assert.EqualValues(t, testCase.uuid, uuid) {
ownerName, repoName, found := strings.Cut(testCase.scope, "/")
diff --git a/tests/integration/cmd_forgejo_test.go b/tests/integration/cmd_forgejo_test.go
deleted file mode 100644
index 76f5a6fc08..0000000000
--- a/tests/integration/cmd_forgejo_test.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-package integration
-
-import (
- "bytes"
- "context"
- "strings"
- "testing"
-
- "code.gitea.io/gitea/cmd/forgejo"
-
- "github.com/urfave/cli/v2"
-)
-
-func cmdForgejoCaptureOutput(t *testing.T, args []string, stdin ...string) (string, error) {
- buf := new(bytes.Buffer)
-
- app := cli.NewApp()
- app.Writer = buf
- app.ErrWriter = buf
- ctx := context.Background()
- ctx = forgejo.ContextSetNoInit(ctx, true)
- ctx = forgejo.ContextSetNoExit(ctx, true)
- ctx = forgejo.ContextSetStdout(ctx, buf)
- ctx = forgejo.ContextSetStderr(ctx, buf)
- if len(stdin) > 0 {
- ctx = forgejo.ContextSetStdin(ctx, strings.NewReader(strings.Join(stdin, "")))
- }
- app.Commands = []*cli.Command{
- forgejo.CmdForgejo(ctx),
- }
- err := app.Run(args)
-
- return buf.String(), err
-}
diff --git a/tests/integration/cmd_keys_test.go b/tests/integration/cmd_keys_test.go
index 61f11c58b0..a3220c13ce 100644
--- a/tests/integration/cmd_keys_test.go
+++ b/tests/integration/cmd_keys_test.go
@@ -4,16 +4,15 @@
package integration
import (
- "bytes"
+ "errors"
"net/url"
+ "os/exec"
"testing"
- "code.gitea.io/gitea/cmd"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
- "github.com/urfave/cli/v2"
)
func Test_CmdKeys(t *testing.T) {
@@ -24,30 +23,30 @@ func Test_CmdKeys(t *testing.T) {
wantErr bool
expectedOutput string
}{
- {"test_empty_1", []string{"keys", "--username=git", "--type=test", "--content=test"}, true, ""},
- {"test_empty_2", []string{"keys", "-e", "git", "-u", "git", "-t", "test", "-k", "test"}, true, ""},
+ {"test_empty_1", []string{"--username=git", "--type=test", "--content=test"}, true, ""},
+ {"test_empty_2", []string{"-e", "git", "-u", "git", "-t", "test", "-k", "test"}, true, ""},
{
"with_key",
- []string{"keys", "-e", "git", "-u", "git", "-t", "ssh-rsa", "-k", "AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM="},
+ []string{"-e", "git", "-u", "git", "-t", "ssh-rsa", "-k", "AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM="},
false,
"# gitea public key\ncommand=\"" + setting.AppPath + " --config=" + util.ShellEscape(setting.CustomConf) + " serv key-1\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM= user2@localhost\n",
},
- {"invalid", []string{"keys", "--not-a-flag=git"}, true, "Incorrect Usage: flag provided but not defined: -not-a-flag\n\n"},
+ {"invalid", []string{"--not-a-flag=git"}, true, "Incorrect Usage: flag provided but not defined: -not-a-flag\n\n"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- out := new(bytes.Buffer)
- app := cli.NewApp()
- app.Writer = out
- app.Commands = []*cli.Command{cmd.CmdKeys}
- cmd.CmdKeys.HideHelp = true
- err := app.Run(append([]string{"prog"}, tt.args...))
+ out, err := runMainApp("keys", tt.args...)
+
+ var exitErr *exec.ExitError
+ if errors.As(err, &exitErr) {
+ t.Log(string(exitErr.Stderr))
+ }
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
- assert.Equal(t, tt.expectedOutput, out.String())
+ assert.Equal(t, tt.expectedOutput, out)
})
}
})
diff --git a/tests/integration/git_test.go b/tests/integration/git_test.go
index e0a8246719..782390002c 100644
--- a/tests/integration/git_test.go
+++ b/tests/integration/git_test.go
@@ -84,6 +84,7 @@ func testGit(t *testing.T, u *url.URL) {
mediaTest(t, &httpContext, little, big, littleLFS, bigLFS)
t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "master", "test/head"))
+ t.Run("InternalReferences", doInternalReferences(&httpContext, dstPath))
t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath))
t.Run("AutoMerge", doAutoPRMerge(&httpContext, dstPath))
t.Run("CreatePRAndSetManuallyMerged", doCreatePRAndSetManuallyMerged(httpContext, httpContext, dstPath, "master", "test-manually-merge"))
@@ -125,6 +126,7 @@ func testGit(t *testing.T, u *url.URL) {
mediaTest(t, &sshContext, little, big, littleLFS, bigLFS)
t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "master", "test/head2"))
+ t.Run("InternalReferences", doInternalReferences(&sshContext, dstPath))
t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath))
t.Run("MergeFork", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
@@ -734,6 +736,20 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
}
}
+func doInternalReferences(ctx *APITestContext, dstPath string) func(t *testing.T) {
+ return func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: ctx.Username, Name: ctx.Reponame})
+ pr1 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{HeadRepoID: repo.ID})
+
+ _, stdErr, gitErr := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments(fmt.Sprintf(":refs/pull/%d/head", pr1.Index)).RunStdString(&git.RunOpts{Dir: dstPath})
+ assert.Error(t, gitErr)
+ assert.Contains(t, stdErr, fmt.Sprintf("remote: Forgejo: The deletion of refs/pull/%d/head is skipped as it's an internal reference.", pr1.Index))
+ assert.Contains(t, stdErr, fmt.Sprintf("[remote rejected] refs/pull/%d/head (hook declined)", pr1.Index))
+ }
+}
+
func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headBranch string) func(t *testing.T) {
return func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go
index e8f28105c1..b087281ff4 100644
--- a/tests/integration/integration_test.go
+++ b/tests/integration/integration_test.go
@@ -17,6 +17,7 @@ import (
"net/http/httptest"
"net/url"
"os"
+ "os/exec"
"path/filepath"
"strconv"
"strings"
@@ -24,6 +25,7 @@ import (
"testing"
"time"
+ "code.gitea.io/gitea/cmd"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
@@ -93,7 +95,43 @@ func NewNilResponseHashSumRecorder() *NilResponseHashSumRecorder {
}
}
+// runMainApp runs the subcommand and returns its standard output. Any returned error will usually be of type *ExitError. If c.Stderr was nil, Output populates ExitError.Stderr.
+func runMainApp(subcommand string, args ...string) (string, error) {
+ return runMainAppWithStdin(nil, subcommand, args...)
+}
+
+// runMainAppWithStdin runs the subcommand and returns its standard output. Any returned error will usually be of type *ExitError. If c.Stderr was nil, Output populates ExitError.Stderr.
+func runMainAppWithStdin(stdin io.Reader, subcommand string, args ...string) (string, error) {
+ // running the main app directly will very likely mess with the testing setup (logger & co.)
+ // hence we run it as a subprocess and capture its output
+ args = append([]string{subcommand}, args...)
+ cmd := exec.Command(os.Args[0], args...)
+ cmd.Env = append(os.Environ(),
+ "GITEA_TEST_CLI=true",
+ "GITEA_CONF="+setting.CustomConf,
+ "GITEA_WORK_DIR="+setting.AppWorkPath)
+ cmd.Stdin = stdin
+ out, err := cmd.Output()
+ return string(out), err
+}
+
func TestMain(m *testing.M) {
+ // GITEA_TEST_CLI is set by runMainAppWithStdin
+ // inspired by https://abhinavg.net/2022/05/15/hijack-testmain/
+ if testCLI := os.Getenv("GITEA_TEST_CLI"); testCLI == "true" {
+ app := cmd.NewMainApp("test-version", "integration-test")
+ args := append([]string{
+ "executable-name", // unused, but expected at position 1
+ "--config", os.Getenv("GITEA_CONF"),
+ },
+ os.Args[1:]..., // skip the executable name
+ )
+ if err := cmd.RunMainApp(app, args...); err != nil {
+ panic(err) // should never happen since RunMainApp exits on error
+ }
+ return
+ }
+
defer log.GetManager().Close()
managerCtx, cancel := context.WithCancel(context.Background())
diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go
index c9ecadca9a..e613538d05 100644
--- a/tests/integration/pull_merge_test.go
+++ b/tests/integration/pull_merge_test.go
@@ -26,11 +26,14 @@ import (
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/hostmatcher"
+ "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/services/pull"
files_service "code.gitea.io/gitea/services/repository/files"
+ webhook_service "code.gitea.io/gitea/services/webhook"
"github.com/stretchr/testify/assert"
)
@@ -83,7 +86,19 @@ func testPullCleanUp(t *testing.T, session *TestSession, user, repo, pullnum str
func retrieveHookTasks(t *testing.T, hookID int64, activateWebhook bool) []*webhook.HookTask {
t.Helper()
if activateWebhook {
- updated, err := db.GetEngine(db.DefaultContext).ID(hookID).Cols("is_active").Update(webhook.Webhook{IsActive: true})
+ s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusNoContent)
+ }))
+ t.Cleanup(s.Close)
+ updated, err := db.GetEngine(db.DefaultContext).ID(hookID).Cols("is_active", "url").Update(webhook.Webhook{
+ IsActive: true,
+ URL: s.URL,
+ })
+
+ // allow webhook deliveries on localhost
+ t.Cleanup(test.MockVariableValue(&setting.Webhook.AllowedHostList, hostmatcher.MatchBuiltinLoopback))
+ webhook_service.Init()
+
assert.Equal(t, int64(1), updated)
assert.NoError(t, err)
}
diff --git a/tests/integration/pull_review_test.go b/tests/integration/pull_review_test.go
index 2daba05ddf..63ec3f9f35 100644
--- a/tests/integration/pull_review_test.go
+++ b/tests/integration/pull_review_test.go
@@ -6,11 +6,20 @@ package integration
import (
"context"
"net/http"
+ "net/url"
"strconv"
+ "strings"
"testing"
- "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/models/db"
+ issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/test"
+ issue_service "code.gitea.io/gitea/services/issue"
+ repo_service "code.gitea.io/gitea/services/repository"
+ files_service "code.gitea.io/gitea/services/repository/files"
"code.gitea.io/gitea/tests"
"github.com/PuerkitoBio/goquery"
@@ -22,17 +31,31 @@ func TestPullView_ReviewerMissed(t *testing.T) {
session := loginUser(t, "user1")
req := NewRequest(t, "GET", "/pulls")
- session.MakeRequest(t, req, http.StatusOK)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
req = NewRequest(t, "GET", "/user2/repo1/pulls/3")
- session.MakeRequest(t, req, http.StatusOK)
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
+
+ // if some reviews are missing, the page shouldn't fail
+ reviews, err := issues_model.FindReviews(db.DefaultContext, issues_model.FindReviewOptions{
+ IssueID: 2,
+ })
+ assert.NoError(t, err)
+ for _, r := range reviews {
+ assert.NoError(t, issues_model.DeleteReview(db.DefaultContext, r))
+ }
+ req = NewRequest(t, "GET", "/user2/repo1/pulls/2")
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
}
-func loadComment(t *testing.T, commentID string) *issues.Comment {
+func loadComment(t *testing.T, commentID string) *issues_model.Comment {
t.Helper()
id, err := strconv.ParseInt(commentID, 10, 64)
assert.NoError(t, err)
- return unittest.AssertExistsAndLoadBean(t, &issues.Comment{ID: id})
+ return unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: id})
}
func TestPullView_ResolveInvalidatedReviewComment(t *testing.T) {
@@ -81,7 +104,7 @@ func TestPullView_ResolveInvalidatedReviewComment(t *testing.T) {
// (to invalidate it properly, one should push a commit which should trigger this logic,
// in the meantime, use this quick-and-dirty trick)
comment := loadComment(t, commentID)
- assert.NoError(t, issues.UpdateCommentInvalidate(context.Background(), &issues.Comment{
+ assert.NoError(t, issues_model.UpdateCommentInvalidate(context.Background(), &issues_model.Comment{
ID: comment.ID,
Invalidated: true,
}))
@@ -143,7 +166,7 @@ func TestPullView_ResolveInvalidatedReviewComment(t *testing.T) {
// (to invalidate it properly, one should push a commit which should trigger this logic,
// in the meantime, use this quick-and-dirty trick)
comment := loadComment(t, commentID)
- assert.NoError(t, issues.UpdateCommentInvalidate(context.Background(), &issues.Comment{
+ assert.NoError(t, issues_model.UpdateCommentInvalidate(context.Background(), &issues_model.Comment{
ID: comment.ID,
Invalidated: true,
}))
@@ -250,3 +273,129 @@ func TestPullView_ResolveInvalidatedReviewComment(t *testing.T) {
assert.Len(t, comments.Nodes, 3) // 1 comment on line 1 + 2 comments on line 3
})
}
+
+func TestPullView_CodeOwner(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+
+ // Create the repo.
+ repo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{
+ Name: "test_codeowner",
+ Readme: "Default",
+ AutoInit: true,
+ ObjectFormatName: git.Sha1ObjectFormat.Name(),
+ DefaultBranch: "master",
+ })
+ assert.NoError(t, err)
+
+ // add CODEOWNERS to default branch
+ _, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
+ OldBranch: repo.DefaultBranch,
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: "create",
+ TreePath: "CODEOWNERS",
+ ContentReader: strings.NewReader("README.md @user5\n"),
+ },
+ },
+ })
+ assert.NoError(t, err)
+
+ t.Run("First Pull Request", func(t *testing.T) {
+ // create a new branch to prepare for pull request
+ _, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
+ NewBranch: "codeowner-basebranch",
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: "update",
+ TreePath: "README.md",
+ ContentReader: strings.NewReader("# This is a new project\n"),
+ },
+ },
+ })
+ assert.NoError(t, err)
+
+ // Create a pull request.
+ session := loginUser(t, "user2")
+ testPullCreate(t, session, "user2", "test_codeowner", false, repo.DefaultBranch, "codeowner-basebranch", "Test Pull Request")
+
+ pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadRepoID: repo.ID, HeadBranch: "codeowner-basebranch"})
+ unittest.AssertExistsIf(t, true, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 5})
+ assert.NoError(t, pr.LoadIssue(db.DefaultContext))
+
+ err := issue_service.ChangeTitle(db.DefaultContext, pr.Issue, user2, "[WIP] Test Pull Request")
+ assert.NoError(t, err)
+ prUpdated1 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
+ assert.NoError(t, prUpdated1.LoadIssue(db.DefaultContext))
+ assert.EqualValues(t, "[WIP] Test Pull Request", prUpdated1.Issue.Title)
+
+ err = issue_service.ChangeTitle(db.DefaultContext, prUpdated1.Issue, user2, "Test Pull Request2")
+ assert.NoError(t, err)
+ prUpdated2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
+ assert.NoError(t, prUpdated2.LoadIssue(db.DefaultContext))
+ assert.EqualValues(t, "Test Pull Request2", prUpdated2.Issue.Title)
+ })
+
+ // change the default branch CODEOWNERS file to change README.md's codeowner
+ _, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: "update",
+ TreePath: "CODEOWNERS",
+ ContentReader: strings.NewReader("README.md @user8\n"),
+ },
+ },
+ })
+ assert.NoError(t, err)
+
+ t.Run("Second Pull Request", func(t *testing.T) {
+ // create a new branch to prepare for pull request
+ _, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
+ NewBranch: "codeowner-basebranch2",
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: "update",
+ TreePath: "README.md",
+ ContentReader: strings.NewReader("# This is a new project2\n"),
+ },
+ },
+ })
+ assert.NoError(t, err)
+
+ // Create a pull request.
+ session := loginUser(t, "user2")
+ testPullCreate(t, session, "user2", "test_codeowner", false, repo.DefaultBranch, "codeowner-basebranch2", "Test Pull Request2")
+
+ pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadBranch: "codeowner-basebranch2"})
+ unittest.AssertExistsIf(t, true, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
+ })
+
+ t.Run("Forked Repo Pull Request", func(t *testing.T) {
+ user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
+ forkedRepo, err := repo_service.ForkRepository(db.DefaultContext, user2, user5, repo_service.ForkRepoOptions{
+ BaseRepo: repo,
+ Name: "test_codeowner_fork",
+ })
+ assert.NoError(t, err)
+
+ // create a new branch to prepare for pull request
+ _, err = files_service.ChangeRepoFiles(db.DefaultContext, forkedRepo, user5, &files_service.ChangeRepoFilesOptions{
+ NewBranch: "codeowner-basebranch-forked",
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: "update",
+ TreePath: "README.md",
+ ContentReader: strings.NewReader("# This is a new forked project\n"),
+ },
+ },
+ })
+ assert.NoError(t, err)
+
+ session := loginUser(t, "user5")
+ testPullCreate(t, session, "user5", "test_codeowner_fork", false, forkedRepo.DefaultBranch, "codeowner-basebranch-forked", "Test Pull Request2")
+
+ pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadBranch: "codeowner-basebranch-forked"})
+ unittest.AssertExistsIf(t, false, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
+ })
+ })
+}
diff --git a/tests/integration/repo_signed_tag_test.go b/tests/integration/repo_signed_tag_test.go
new file mode 100644
index 0000000000..a2904dc075
--- /dev/null
+++ b/tests/integration/repo_signed_tag_test.go
@@ -0,0 +1,107 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+ "fmt"
+ "net/http"
+ "os"
+ "os/exec"
+ "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"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/graceful"
+ repo_module "code.gitea.io/gitea/modules/repository"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/tests"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRepoSSHSignedTags(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+
+ // Preparations
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ repo, _, f := CreateDeclarativeRepo(t, user, "", nil, nil, nil)
+ defer f()
+
+ // Set up an SSH key for the tagger
+ tmpDir := t.TempDir()
+ err := os.Chmod(tmpDir, 0o700)
+ assert.NoError(t, err)
+
+ signingKey := fmt.Sprintf("%s/ssh_key", tmpDir)
+
+ cmd := exec.Command("ssh-keygen", "-t", "ed25519", "-N", "", "-f", signingKey)
+ err = cmd.Run()
+ assert.NoError(t, err)
+
+ // Set up git config for the tagger
+ _ = git.NewCommand(git.DefaultContext, "config", "user.name").AddDynamicArguments(user.Name).Run(&git.RunOpts{Dir: repo.RepoPath()})
+ _ = git.NewCommand(git.DefaultContext, "config", "user.email").AddDynamicArguments(user.Email).Run(&git.RunOpts{Dir: repo.RepoPath()})
+ _ = git.NewCommand(git.DefaultContext, "config", "gpg.format", "ssh").Run(&git.RunOpts{Dir: repo.RepoPath()})
+ _ = git.NewCommand(git.DefaultContext, "config", "user.signingkey").AddDynamicArguments(signingKey).Run(&git.RunOpts{Dir: repo.RepoPath()})
+
+ // Open the git repo
+ gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
+ defer gitRepo.Close()
+
+ // Create a signed tag
+ err = git.NewCommand(git.DefaultContext, "tag", "-s", "-m", "this is a signed tag", "ssh-signed-tag").Run(&git.RunOpts{Dir: repo.RepoPath()})
+ assert.NoError(t, err)
+
+ // Sync the tag to the DB
+ repo_module.SyncRepoTags(graceful.GetManager().ShutdownContext(), repo.ID)
+
+ // Helper functions
+ assertTagSignedStatus := func(t *testing.T, isSigned bool) {
+ t.Helper()
+
+ req := NewRequestf(t, "GET", "%s/releases/tag/ssh-signed-tag", repo.HTMLURL())
+ resp := MakeRequest(t, req, http.StatusOK)
+ doc := NewHTMLParser(t, resp.Body)
+
+ doc.AssertElement(t, ".tag-signature-row .gitea-unlock", !isSigned)
+ doc.AssertElement(t, ".tag-signature-row .gitea-lock", isSigned)
+ }
+
+ t.Run("unverified", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ assertTagSignedStatus(t, false)
+ })
+
+ t.Run("verified", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ // Upload the signing key
+ keyData, err := os.ReadFile(fmt.Sprintf("%s.pub", signingKey))
+ assert.NoError(t, err)
+ key := string(keyData)
+
+ session := loginUser(t, user.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)
+
+ req := NewRequestWithJSON(t, "POST", "/api/v1/user/keys", &api.CreateKeyOption{
+ Key: key,
+ Title: "test key",
+ }).AddTokenAuth(token)
+ resp := MakeRequest(t, req, http.StatusCreated)
+
+ var pubkey *api.PublicKey
+ DecodeJSON(t, resp, &pubkey)
+
+ // Mark the key as verified
+ db.GetEngine(db.DefaultContext).Exec("UPDATE `public_key` SET verified = true WHERE id = ?", pubkey.ID)
+
+ // Check the tag page
+ assertTagSignedStatus(t, true)
+ })
+}
diff --git a/tools/lint-templates-svg.js b/tools/lint-templates-svg.js
new file mode 100755
index 0000000000..72f756400d
--- /dev/null
+++ b/tools/lint-templates-svg.js
@@ -0,0 +1,26 @@
+#!/usr/bin/env node
+import {readdirSync, readFileSync} from 'node:fs';
+import {parse, relative} from 'node:path';
+import {fileURLToPath} from 'node:url';
+import {exit} from 'node:process';
+import fastGlob from 'fast-glob';
+
+const knownSvgs = new Set();
+for (const file of readdirSync(new URL('../public/assets/img/svg', import.meta.url))) {
+ knownSvgs.add(parse(file).name);
+}
+
+const rootPath = fileURLToPath(new URL('..', import.meta.url));
+let hadErrors = false;
+
+for (const file of fastGlob.sync(fileURLToPath(new URL('../templates/**/*.tmpl', import.meta.url)))) {
+ const content = readFileSync(file, 'utf8');
+ for (const [_, name] of content.matchAll(/svg ["'`]([^"'`]+)["'`]/g)) {
+ if (!knownSvgs.has(name)) {
+ console.info(`SVG "${name}" not found, used in ${relative(rootPath, file)}`);
+ hadErrors = true;
+ }
+ }
+}
+
+exit(hadErrors ? 1 : 0);
diff --git a/web_src/css/base.css b/web_src/css/base.css
index f78fd920e5..05ab0255e8 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -21,6 +21,7 @@
--border-radius-circle: 50%;
--opacity-disabled: 0.55;
--height-loading: 16rem;
+ --repo-header-issue-min-height: 41px;
--min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */
--tab-size: 4;
}
@@ -62,10 +63,17 @@ pre,
code,
kbd,
samp {
- font-size: 0.9em; /* compensate for monospace fonts being usually slightly larger */
font-family: var(--fonts-monospace);
}
+pre,
+code,
+kbd,
+samp,
+.tw-font-mono {
+ font-size: 0.95em; /* compensate for monospace fonts being usually slightly larger */
+}
+
b,
strong,
h1,
@@ -226,10 +234,14 @@ h1.error-code {
a {
color: var(--color-primary);
cursor: pointer;
- text-decoration: none;
+ text-decoration-line: none;
text-decoration-skip-ink: all;
}
+a:hover {
+ text-decoration-line: underline;
+}
+
/* a = always colored, underlined on hover */
/* a.muted = colored on hover, underlined on hover */
/* a.suppressed = never colored, underlined on hover */
@@ -256,7 +268,7 @@ a.suppressed:hover {
}
a.silenced:hover {
- text-decoration: none;
+ text-decoration-line: none;
}
a.label,
@@ -264,7 +276,7 @@ a.label,
.ui .menu a,
.ui.cards a.card,
.issue-keyword a {
- text-decoration: none !important;
+ text-decoration-line: none !important;
}
.ui.search > .results {
@@ -300,12 +312,8 @@ a.label,
.inline-code-block {
padding: 2px 4px;
- border-radius: var(--border-radius-medium);
- background-color: var(--color-markup-code-block);
-}
-
-.ui.dividing.header {
- border-bottom-color: var(--color-secondary);
+ border-radius: .24em;
+ background-color: var(--color-label-bg);
}
/* fix Fomantic's line-height cutting off "g" on Windows Chrome with Segoe UI */
@@ -363,10 +371,15 @@ ol.ui.list li,
border-right-color: var(--color-primary);
}
+.ui.menu {
+ display: flex;
+}
+
.ui.menu,
.ui.vertical.menu {
background: var(--color-menu);
border-color: var(--color-secondary);
+ box-shadow: none;
}
.ui.menu .item {
@@ -583,26 +596,6 @@ ol.ui.list li,
visibility: visible !important;
}
-.ui.error.header {
- background: var(--color-error-bg) !important;
- color: var(--color-error-text) !important;
- border-color: var(--color-error-border) !important;
-}
-
-.ui.error.segment {
- border-color: var(--color-error-border) !important;
-}
-
-.ui.warning.header {
- background: var(--color-warning-bg) !important;
- color: var(--color-warning-text) !important;
- border-color: var(--color-warning-border) !important;
-}
-
-.ui.warning.segment {
- border-color: var(--color-warning-border) !important;
-}
-
.ui.selection.active.dropdown,
.ui.selection.active.dropdown:hover,
.ui.selection.active.dropdown .menu,
@@ -646,86 +639,6 @@ ol.ui.list li,
color: var(--color-primary);
}
-.ui.attached.table {
- border-color: var(--color-secondary);
-}
-
-.ui.table {
- color: var(--color-text);
- background: var(--color-box-body);
- border-color: var(--color-secondary);
- text-align: start; /* Override fomantic's `text-align: left` to make RTL work via HTML `dir="auto"` */
-}
-
-.ui.table th,
-.ui.table td {
- transition: none;
-}
-
-.ui.table > tr > td,
-.ui.table > tbody > tr > td {
- border-top-color: var(--color-secondary-alpha-50);
-}
-
-.ui.striped.table > tr:nth-child(2n),
-.ui.striped.table > tbody > tr:nth-child(2n),
-.ui.basic.striped.table > tbody > tr:nth-child(2n) {
- background: var(--color-light);
-}
-
-.ui.ui.ui.ui.table tr.active,
-.ui.ui.table td.active {
- color: var(--color-text);
- background: var(--color-active);
-}
-
-.ui.ui.selectable.table > tbody > tr:hover,
-.ui.table tbody tr td.selectable:hover {
- color: var(--color-text);
- background-color: var(--color-secondary-alpha-40);
-}
-
-.ui.ui.ui.ui.table tr.grey:not(.marked),
-.ui.ui.table td.grey:not(.marked) {
- background: var(--color-body);
- color: var(--color-text);
-}
-
-.ui.table > thead > tr > th {
- background: var(--color-box-header);
- border-color: var(--color-secondary);
- color: var(--color-text);
-}
-
-.ui.basic.table > tbody > tr {
- border-color: var(--color-secondary);
-}
-
-.ui.table > tfoot > tr > th,
-.ui.table > tfoot > tr > td {
- border-color: var(--color-secondary);
- background: var(--color-box-body);
- color: var(--color-text);
-}
-
-/* reduce table padding, needed especially for dense admin tables */
-.ui.table > thead > tr > th,
-.ui.table > tbody > tr > td,
-.ui.table > tr > td {
- padding: 6px 5px;
-}
-/* use more horizontal padding on first and last items for visuals */
-.ui.table > thead > tr > th:first-of-type,
-.ui.table > tbody > tr > td:first-of-type,
-.ui.table > tr > td:first-of-type {
- padding-left: 10px;
-}
-.ui.table > thead > tr > th:last-of-type,
-.ui.table > tbody > tr > td:last-of-type,
-.ui.table > tr > td:last-of-type {
- padding-right: 10px;
-}
-
img.ui.avatar,
.ui.avatar img,
.ui.avatar svg {
@@ -749,40 +662,16 @@ img.ui.avatar,
padding-bottom: 80px;
}
-.page-content.new:is(.repo,.migrate,.org),
-.page-content.profile:is(.user,.organization) {
- padding-top: 15px;
-}
-
-/* overwrite semantic width of containers inside the main page content div (div with class "page-content") */
-.page-content .ui.ui.ui.container:not(.fluid) {
- width: 1280px;
- max-width: calc(100% - 64px);
- margin-left: auto;
- margin-right: auto;
+/* add margin below .secondary nav when it is the first child */
+.page-content > :first-child.secondary-nav {
+ margin-bottom: 14px;
}
-.ui.container.fluid.padded {
- padding: 0 32px;
-}
-
-/* enable fluid page widths for medium size viewports */
-@media (min-width: 768px) and (max-width: 1200px) {
- .page-content .ui.ui.ui.container:not(.fluid) {
- max-width: calc(100% - 32px);
- }
- .ui.container.fluid.padded {
- padding: 0 16px;
- }
-}
-
-@media (max-width: 767.98px) {
- .page-content .ui.ui.ui.container:not(.fluid) {
- max-width: calc(100% - 16px);
- }
- .ui.container.fluid.padded {
- padding: 0 8px;
- }
+/* add padding to all content when there is no .secondary.nav. this uses padding instead of
+ margin because with the negative margin on .ui.grid we would have to set margin-top: 0,
+ but that does not work universally for all pages */
+.page-content > :first-child:not(.secondary-nav) {
+ padding-top: 14px;
}
.ui.pagination.menu .active.item {
@@ -965,20 +854,6 @@ input:-webkit-autofill:active,
font-weight: var(--font-weight-normal);
}
-.ui.floating.label {
- z-index: 10;
-}
-
-.ui.transparent.label {
- background-color: transparent;
-}
-
-.ui.menu,
-.ui.vertical.menu,
-.ui.segment {
- box-shadow: none;
-}
-
/* replace fomantic popover box shadows */
.ui.dropdown .menu,
.ui.upward.dropdown > .menu,
@@ -1013,14 +888,6 @@ input:-webkit-autofill:active,
width: 100%;
}
-.ui.dropdown .menu > .item > .floating.label {
- z-index: 11;
-}
-
-.ui.dropdown .menu .menu > .item > .floating.label {
- z-index: 21;
-}
-
.ui.dropdown .menu > .header {
font-size: 0.8em;
}
@@ -1072,23 +939,6 @@ input:-webkit-autofill:active,
vertical-align: middle;
}
-.ui .info.segment.top h3,
-.ui .info.segment.top h4 {
- margin-top: 0;
-}
-
-.ui .info.segment.top h3:last-child {
- margin-top: 4px;
-}
-
-.ui .info.segment.top > :last-child {
- margin-bottom: 0;
-}
-
-.ui .normal.header {
- font-weight: var(--font-weight-normal);
-}
-
.ui .form .autofill-dummy {
position: absolute;
width: 1px;
@@ -1246,17 +1096,6 @@ input:-webkit-autofill:active,
margin-right: 0;
}
-.ui.icon.header svg {
- width: 3em;
- height: 3em;
- float: none;
- display: block;
- line-height: var(--line-height-default);
- padding: 0;
- margin: 0 auto 0.5rem;
- opacity: 1;
-}
-
.ui.floating.dropdown .overflow.menu .scrolling.menu.items {
border-radius: 0 !important;
box-shadow: none !important;
@@ -1284,15 +1123,6 @@ input:-webkit-autofill:active,
border-radius: var(--border-radius);
}
-.attention-header {
- padding: 0.5em 0.75em !important;
- color: var(--color-text) !important;
-}
-
-.attention-header :first-child {
- display: flex;
-}
-
.attention-icon {
margin: auto 0.5em auto 0;
}
@@ -1337,7 +1167,6 @@ strong.attention-caution, svg.attention-caution {
}
overflow-menu {
- margin-bottom: 15px !important;
border-bottom: 1px solid var(--color-secondary) !important;
display: flex;
}
@@ -1351,6 +1180,10 @@ overflow-menu .overflow-menu-items .item {
margin-bottom: 0 !important; /* reset fomantic's margin, because the active menu has special bottom border */
}
+overflow-menu .ui.label {
+ margin-left: 7px !important; /* save some space */
+}
+
.activity-bar-graph {
background-color: var(--color-primary);
color: var(--color-primary-contrast);
@@ -1384,57 +1217,21 @@ overflow-menu .overflow-menu-items .item {
margin-top: 1px;
}
-.ui.label {
- padding: 0.3em 0.5em;
- transition: none;
- white-space: nowrap;
-}
-
-.ui.label,
-.ui.menu .item > .label,
-.ui.grey.labels .label,
-.ui.ui.ui.grey.label {
+.ui.menu .item > .label {
background: var(--color-label-bg);
color: var(--color-label-text);
}
-.ui.label > a {
- opacity: .75; /* increase contrast over default fomantic .5 */
-}
-
-.ui.active.label {
- background: var(--color-label-active-bg);
- border-color: var(--color-label-active-bg);
- color: var(--color-label-text);
-}
-
-.ui.labels a.label:hover,
-a.ui.label:hover {
- background: var(--color-label-hover-bg);
- border-color: var(--color-label-hover-bg);
- color: var(--color-label-text);
-}
-
-.ui.labels a.active.label:hover,
-a.ui.active.label:hover {
- background: var(--color-label-active-bg);
- border-color: var(--color-label-active-bg);
- color: var(--color-label-text);
-}
-
.lines-blame-btn {
- padding-left: 10px;
- padding-right: 10px;
- text-align: right !important;
- background-color: var(--color-code-sidebar-bg);
- width: 2%;
+ padding: 0 0 0 5px;
+ display: flex;
+ justify-content: center;
}
.lines-num {
- padding-left: 10px;
- padding-right: 10px;
+ padding: 0 8px;
text-align: right !important;
- color: var(--color-text-light-1);
+ color: var(--color-text-light-2);
width: 1%;
font-family: var(--fonts-monospace);
}
@@ -1488,22 +1285,34 @@ a.ui.active.label:hover {
}
.lines-code {
- background-color: var(--color-code-bg);
padding-left: 5px;
}
-.lines-code.active,
-.lines-code .active {
- background: var(--color-active-line) !important;
+.file-view tr.active {
+ color: inherit !important;
+ background: inherit !important;
}
-.blame .lines-num {
- padding: 0 !important;
- background-color: var(--color-code-sidebar-bg);
+.file-view tr.active .lines-num,
+.file-view tr.active .lines-code {
+ background: var(--color-highlight-bg) !important;
}
-.blame .lines-code {
- padding: 0 !important;
+.file-view tr.active:last-of-type .lines-code {
+ border-bottom-right-radius: var(--border-radius);
+}
+
+.file-view tr.active .lines-num {
+ position: relative;
+}
+
+.file-view tr.active .lines-num::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ width: 2px;
+ height: 100%;
+ background: var(--color-highlight-fg);
}
.code-inner {
@@ -1514,24 +1323,21 @@ a.ui.active.label:hover {
}
.blame .code-inner {
- white-space: pre;
- word-break: normal;
- word-wrap: normal; /* not using overflow-wrap because safari does not treat is an an alias */
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
}
.lines-commit {
vertical-align: top;
- color: var(--color-text-light-2);
+ color: var(--color-text-light-1);
padding: 0 !important;
- background: var(--color-code-sidebar-bg);
width: 1%;
}
.lines-commit .blame-info {
- width: 350px;
- max-width: 350px;
+ width: min(26vw, 300px);
display: block;
- padding: 0 0 0 10px;
+ padding: 0 0 0 6px;
line-height: 20px;
box-sizing: content-box;
}
@@ -1553,11 +1359,10 @@ a.ui.active.label:hover {
flex-shrink: 0;
}
-.lines-commit .ui.avatar {
- height: 18px;
- width: 18px;
- display: block;
- margin-top: 1px;
+.blame-avatar {
+ display: flex;
+ align-items: center;
+ margin-right: 4px;
}
.top-line-blame {
@@ -1573,148 +1378,13 @@ a.ui.active.label:hover {
border-bottom: 1px solid var(--color-secondary);
}
-.code-view table {
- width: 100%;
-}
-
-.ui.primary.label,
-.ui.primary.labels .label,
-.ui.ui.ui.primary.label {
- background-color: var(--color-primary);
- border-color: var(--color-primary-dark-2);
-}
-
-.ui.basic.labels .primary.label,
-.ui.ui.ui.basic.primary.label {
- background: transparent;
- border-color: var(--color-primary);
- color: var(--color-primary);
-}
-
-.ui.basic.labels a.primary.label:hover,
-a.ui.ui.ui.basic.primary.label:hover {
- background: var(--color-hover);
- border-color: var(--color-primary-dark-1);
- color: var(--color-primary-dark-1);
-}
-
-.ui.basic.labels .secondary.label,
-.ui.ui.ui.basic.secondary.label {
- background: transparent;
- border-color: var(--color-secondary);
- color: var(--color-secondary);
-}
-
-.ui.basic.labels .orange.label,
-.ui.ui.ui.basic.orange.label {
- background: transparent;
- border-color: var(--color-orange);
- color: var(--color-orange);
-}
-
-.ui.basic.labels .green.label,
-.ui.ui.ui.basic.green.label {
- background: transparent;
- border-color: var(--color-green);
- color: var(--color-green);
-}
-
-.ui.basic.labels .olive.label,
-.ui.ui.ui.basic.olive.label {
- background: transparent;
- border-color: var(--color-olive);
- color: var(--color-olive);
-}
-
-.ui.basic.labels .teal.label,
-.ui.ui.ui.basic.teal.label {
- background: transparent;
- border-color: var(--color-teal);
- color: var(--color-teal);
-}
-
-.ui.basic.labels .blue.label,
-.ui.ui.ui.basic.blue.label {
- background: transparent;
- border-color: var(--color-blue);
- color: var(--color-blue);
-}
-
-.ui.basic.labels .violet.label,
-.ui.ui.ui.basic.violet.label {
- background: transparent;
- border-color: var(--color-violet);
- color: var(--color-violet);
-}
-
-.ui.basic.labels .purple.label,
-.ui.ui.ui.basic.purple.label {
- background: transparent;
- border-color: var(--color-purple);
- color: var(--color-purple);
-}
-
-.ui.basic.labels .pink.label,
-.ui.ui.ui.basic.pink.label {
- background: transparent;
- border-color: var(--color-pink);
- color: var(--color-pink);
-}
-
-.ui.basic.labels .red.label,
-.ui.ui.ui.basic.red.label {
- background: transparent;
- border-color: var(--color-red);
- color: var(--color-red);
-}
-
-.ui.basic.labels .brown.label,
-.ui.ui.ui.basic.brown.label {
- background: transparent;
- border-color: var(--color-brown);
- color: var(--color-brown);
-}
-
-.ui.basic.labels .yellow.label,
-.ui.ui.ui.basic.yellow.label {
- background: transparent;
- border-color: var(--color-yellow);
- color: var(--color-yellow);
-}
-
-.ui.basic.labels .grey.label,
-.ui.ui.ui.basic.grey.label {
- background: transparent;
- border-color: var(--color-grey);
- color: var(--color-grey);
-}
-
-.ui.basic.labels .black.label,
-.ui.ui.ui.basic.black.label {
- background: transparent;
- border-color: var(--color-black);
- color: var(--color-black);
-}
-
-.ui.basic.labels .label,
-.ui.basic.label,
-.ui.secondary.labels .ui.basic.label {
- background: var(--color-button);
- border-color: var(--color-light-border);
- color: var(--color-text-light);
-}
-
-.ui.basic.labels a.label:hover,
-a.ui.basic.label:hover {
- color: var(--color-text);
- border-color: var(--color-light-border);
- background: var(--color-hover);
+.code-view {
+ background: var(--color-code-bg);
+ border-radius: var(--border-radius);
}
-.ui.label > img {
- width: auto !important;
- vertical-align: middle;
- height: 2.1666em !important;
+.code-view table {
+ width: 100%;
}
.migrate .svg.gitea-git {
@@ -1728,56 +1398,6 @@ a.ui.basic.label:hover {
width: 14px;
}
-.ui.label > .color-icon {
- margin-left: 0;
-}
-
-.ui.segment,
-.ui.segments,
-.ui.attached.segment {
- background: var(--color-box-body);
- color: var(--color-text);
- border-color: var(--color-secondary);
-}
-
-.ui.segments > .segment {
- border-color: var(--color-secondary);
-}
-
-.ui.secondary.segment {
- background: var(--color-secondary-bg);
- color: var(--color-text-light);
-}
-
-.ui.attached.header {
- position: relative;
- background: var(--color-box-header);
- border-color: var(--color-secondary);
-}
-
-/* fix misaligned right buttons on box headers */
-.ui.attached.header > .ui.right {
- position: absolute;
- right: 0.78571429rem;
- top: 0;
- bottom: 0;
- display: flex;
- align-items: center;
- gap: 0.25em;
-}
-
-/* the default ".ui.attached.header > .ui.right" is only able to contain "tiny" buttons, other buttons are too large */
-.ui.attached.header > .ui.right .ui.tiny.button {
- padding: 6px 10px;
- font-weight: var(--font-weight-normal);
-}
-
-/* if a .top.attached.header is followed by a .segment, add some margin */
-.ui.segments + .ui.top.attached.header,
-.ui.attached.segment + .ui.top.attached.header {
- margin-top: 1rem;
-}
-
.rss-icon {
display: inline-flex;
color: var(--color-text-light-1);
@@ -1833,24 +1453,10 @@ table th[data-sortt-desc] .svg {
background: var(--color-secondary-dark-1) !important;
}
-/* https://github.com/go-gitea/gitea/pull/11486 */
-.ui.sub.header {
- text-transform: none;
-}
-
.ui.tabular.menu {
border-color: var(--color-secondary);
}
-.ui.tabular.menu .item {
- padding: 11px 12px;
- color: var(--color-text-light-2);
-}
-
-.ui.tabular.menu .item:hover {
- color: var(--color-text);
-}
-
.ui.tabular.menu .active.item,
.ui.tabular.menu .active.item:hover {
background: var(--color-body);
@@ -1867,31 +1473,34 @@ table th[data-sortt-desc] .svg {
border-color: var(--color-secondary);
}
+.ui.tabular.menu .item,
.ui.secondary.pointing.menu .item {
+ padding: 11px 12px !important;
color: var(--color-text-light-2);
}
-.ui.secondary.pointing.menu .active.item,
-.ui.secondary.pointing.menu .active.item:hover, .ui.secondary.pointing.menu .active.item:focus,
-.ui.secondary.pointing.menu .dropdown.item:hover, .ui.secondary.pointing.menu .dropdown.item:focus,
+.ui.tabular.menu .item:hover,
.ui.secondary.pointing.menu a.item:hover, .ui.secondary.pointing.menu a.item:focus {
- color: var(--color-text-dark);
-}
-
-.ui.header {
color: var(--color-text);
}
-.ui.header .ui.label {
- margin-left: 0.25rem;
+.ui.secondary.pointing.menu .active.item,
+.ui.secondary.pointing.menu .active.item:hover, .ui.secondary.pointing.menu .active.item:focus,
+.ui.secondary.pointing.menu .dropdown.item:hover, .ui.secondary.pointing.menu .dropdown.item:focus {
+ color: var(--color-text-dark);
}
-.ui.header > .ui.label.compact {
- margin-top: inherit;
+.ui.tabular.menu .active.item,
+.ui.secondary.pointing.menu .active.item,
+.resize-for-semibold::before {
+ font-weight: var(--font-weight-semibold);
}
-.ui.header .sub.header {
- color: var(--color-text-light-1);
+.resize-for-semibold::before {
+ content: attr(data-text);
+ visibility: hidden;
+ display: block;
+ height: 0;
}
.flash-error details code,
@@ -1986,7 +1595,6 @@ table th[data-sortt-desc] .svg {
.btn,
.ui.ui.button,
.ui.ui.dropdown,
-.ui.ui.label,
.flex-items-inline > .item,
.flex-text-inline {
display: inline-flex;
@@ -2003,10 +1611,6 @@ table th[data-sortt-desc] .svg {
vertical-align: middle;
}
-.ui.ui.circular.label {
- justify-content: center;
-}
-
.ui.ui.labeled.button {
gap: 0;
align-items: stretch;
diff --git a/web_src/css/chroma/base.css b/web_src/css/chroma/base.css
index 26d128775f..bce13332f8 100644
--- a/web_src/css/chroma/base.css
+++ b/web_src/css/chroma/base.css
@@ -1,7 +1,3 @@
-.chroma {
- background-color: var(--color-code-bg);
-}
-
/* LineTableTD */
.chroma .lntd {
vertical-align: top;
diff --git a/web_src/css/dashboard.css b/web_src/css/dashboard.css
index 6271a99c29..4bb9fa38bf 100644
--- a/web_src/css/dashboard.css
+++ b/web_src/css/dashboard.css
@@ -28,23 +28,6 @@
width: 75%;
}
-.dashboard.feeds .filter.menu .item .floating.label,
-.dashboard.issues .filter.menu .item .floating.label {
- top: 7px;
- left: 90%;
- width: 15%;
-}
-
-@media (max-width: 767.98px) {
- .dashboard.feeds .filter.menu .item .floating.label,
- .dashboard.issues .filter.menu .item .floating.label {
- top: 10px;
- left: auto;
- width: auto;
- right: 13px;
- }
-}
-
/* Sort */
.dashboard.feeds .filter.menu .jump.item,
.dashboard.issues .filter.menu .jump.item {
@@ -77,22 +60,22 @@
margin: 0 1px; /* Accommodate for Semantic's 1px hacks on .attached elements */
}
-.dashboard .dashboard-navbar {
- padding: 4px 12px;
+.dashboard .secondary-nav {
+ padding: 1px 12px; /* match .overflow-menu-items in height */
}
-.dashboard .dashboard-navbar .right.menu {
+.dashboard .secondary-nav .right.menu {
gap: .35714286em;
}
-.dashboard .dashboard-navbar .right.menu div.item {
+.dashboard .secondary-nav .right.menu div.item {
padding-left: 0.5rem;
}
-.dashboard .dashboard-navbar .org-visibility .label {
+.dashboard .secondary-nav .org-visibility .label {
margin-left: 5px;
}
-.dashboard .dashboard-navbar .ui.dropdown {
+.dashboard .secondary-nav .ui.dropdown {
max-width: 100%;
}
diff --git a/web_src/css/explore.css b/web_src/css/explore.css
index 08858337c0..5cdee823c0 100644
--- a/web_src/css/explore.css
+++ b/web_src/css/explore.css
@@ -1,10 +1,8 @@
-.explore .navbar {
- margin-bottom: 15px !important;
- background-color: var(--color-header-wrapper) !important;
+.explore .secondary-nav {
border-width: 1px !important;
}
-.explore .navbar .svg {
+.explore .secondary-nav .svg {
width: 16px;
text-align: center;
margin-right: 5px;
diff --git a/web_src/css/features/projects.css b/web_src/css/features/projects.css
index f85430a2a8..30df994c38 100644
--- a/web_src/css/features/projects.css
+++ b/web_src/css/features/projects.css
@@ -19,6 +19,7 @@
overflow: visible;
display: flex;
flex-direction: column;
+ cursor: default;
}
.project-column-header {
@@ -46,6 +47,7 @@
.project-column-title {
background: none !important;
line-height: 1.25 !important;
+ cursor: inherit;
}
.project-column > .cards {
@@ -92,6 +94,7 @@
}
.card-ghost {
+ border-color: var(--color-secondary-dark-4) !important;
border-style: dashed !important;
background: none !important;
}
diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css
index c9c051faf4..13962f19d7 100644
--- a/web_src/css/helpers.css
+++ b/web_src/css/helpers.css
@@ -3,11 +3,6 @@ Gitea's tailwind-style CSS helper classes have `gt-` prefix.
Gitea's private styles use `g-` prefix.
*/
-.gt-mono {
- font-family: var(--fonts-monospace) !important;
- font-size: .95em !important; /* compensate for monospace fonts being usually slightly larger */
-}
-
.gt-word-break {
word-wrap: break-word !important;
word-break: break-word; /* compat: Safari */
@@ -44,116 +39,18 @@ Gitea's private styles use `g-` prefix.
.interact-bg:hover { background: var(--color-hover) !important; }
.interact-bg:active { background: var(--color-active) !important; }
-.gt-m-0 { margin: 0 !important; }
-.gt-m-1 { margin: .125rem !important; }
-.gt-m-2 { margin: .25rem !important; }
-.gt-m-3 { margin: .5rem !important; }
-.gt-m-4 { margin: 1rem !important; }
-.gt-m-5 { margin: 2rem !important; }
-
-.gt-ml-0 { margin-left: 0 !important; }
-.gt-ml-1 { margin-left: .125rem !important; }
-.gt-ml-2 { margin-left: .25rem !important; }
-.gt-ml-3 { margin-left: .5rem !important; }
-.gt-ml-4 { margin-left: 1rem !important; }
-.gt-ml-5 { margin-left: 2rem !important; }
-
-.gt-mr-0 { margin-right: 0 !important; }
-.gt-mr-1 { margin-right: .125rem !important; }
-.gt-mr-2 { margin-right: .25rem !important; }
-.gt-mr-3 { margin-right: .5rem !important; }
-.gt-mr-4 { margin-right: 1rem !important; }
-.gt-mr-5 { margin-right: 2rem !important; }
-
-.gt-mt-0 { margin-top: 0 !important; }
-.gt-mt-1 { margin-top: .125rem !important; }
-.gt-mt-2 { margin-top: .25rem !important; }
-.gt-mt-3 { margin-top: .5rem !important; }
-.gt-mt-4 { margin-top: 1rem !important; }
-.gt-mt-5 { margin-top: 2rem !important; }
-
-.gt-mb-0 { margin-bottom: 0 !important; }
-.gt-mb-1 { margin-bottom: .125rem !important; }
-.gt-mb-2 { margin-bottom: .25rem !important; }
-.gt-mb-3 { margin-bottom: .5rem !important; }
-.gt-mb-4 { margin-bottom: 1rem !important; }
-.gt-mb-5 { margin-bottom: 2rem !important; }
-
-.gt-mx-0 { margin-left: 0 !important; margin-right: 0 !important; }
-.gt-mx-1 { margin-left: .125rem !important; margin-right: .125rem !important; }
-.gt-mx-2 { margin-left: .25rem !important; margin-right: .25rem !important; }
-.gt-mx-3 { margin-left: .5rem !important; margin-right: .5rem !important; }
-.gt-mx-4 { margin-left: 1rem !important; margin-right: 1rem !important; }
-.gt-mx-5 { margin-left: 2rem !important; margin-right: 2rem !important; }
-
-.gt-my-0 { margin-top: 0 !important; margin-bottom: 0 !important; }
-.gt-my-1 { margin-top: .125rem !important; margin-bottom: .125rem !important; }
-.gt-my-2 { margin-top: .25rem !important; margin-bottom: .25rem !important; }
-.gt-my-3 { margin-top: .5rem !important; margin-bottom: .5rem !important; }
-.gt-my-4 { margin-top: 1rem !important; margin-bottom: 1rem !important; }
-.gt-my-5 { margin-top: 2rem !important; margin-bottom: 2rem !important; }
-
-.gt-p-0 { padding: 0 !important; }
-.gt-p-1 { padding: .125rem !important; }
-.gt-p-2 { padding: .25rem !important; }
-.gt-p-3 { padding: .5rem !important; }
-.gt-p-4 { padding: 1rem !important; }
-.gt-p-5 { padding: 2rem !important; }
-
-.gt-pl-0 { padding-left: 0 !important; }
-.gt-pl-1 { padding-left: .125rem !important; }
-.gt-pl-2 { padding-left: .25rem !important; }
-.gt-pl-3 { padding-left: .5rem !important; }
-.gt-pl-4 { padding-left: 1rem !important; }
-.gt-pl-5 { padding-left: 2rem !important; }
-
-.gt-pr-0 { padding-right: 0 !important; }
-.gt-pr-1 { padding-right: .125rem !important; }
-.gt-pr-2 { padding-right: .25rem !important; }
-.gt-pr-3 { padding-right: .5rem !important; }
-.gt-pr-4 { padding-right: 1rem !important; }
-.gt-pr-5 { padding-right: 2rem !important; }
-
-.gt-pt-0 { padding-top: 0 !important; }
-.gt-pt-1 { padding-top: .125rem !important; }
-.gt-pt-2 { padding-top: .25rem !important; }
-.gt-pt-3 { padding-top: .5rem !important; }
-.gt-pt-4 { padding-top: 1rem !important; }
-.gt-pt-5 { padding-top: 2rem !important; }
-
-.gt-pb-0 { padding-bottom: 0 !important; }
-.gt-pb-1 { padding-bottom: .125rem !important; }
-.gt-pb-2 { padding-bottom: .25rem !important; }
-.gt-pb-3 { padding-bottom: .5rem !important; }
-.gt-pb-4 { padding-bottom: 1rem !important; }
-.gt-pb-5 { padding-bottom: 2rem !important; }
-
-.gt-px-0 { padding-left: 0 !important; padding-right: 0 !important; }
-.gt-px-1 { padding-left: .125rem !important; padding-right: .125rem !important; }
-.gt-px-2 { padding-left: .25rem !important; padding-right: .25rem !important; }
-.gt-px-3 { padding-left: .5rem !important; padding-right: .5rem !important; }
-.gt-px-4 { padding-left: 1rem !important; padding-right: 1rem !important; }
-.gt-px-5 { padding-left: 2rem !important; padding-right: 2rem !important; }
-
-.gt-py-0 { padding-top: 0 !important; padding-bottom: 0 !important; }
-.gt-py-1 { padding-top: .125rem !important; padding-bottom: .125rem !important; }
-.gt-py-2 { padding-top: .25rem !important; padding-bottom: .25rem !important; }
-.gt-py-3 { padding-top: .5rem !important; padding-bottom: .5rem !important; }
-.gt-py-4 { padding-top: 1rem !important; padding-bottom: 1rem !important; }
-.gt-py-5 { padding-top: 2rem !important; padding-bottom: 2rem !important; }
-
/*
-gt-hidden must win all other "display: xxx !important" classes to get the chance to "hide" an element.
+tw-hidden must win all other "display: xxx !important" classes to get the chance to "hide" an element.
do not use:
* "[hidden]" attribute: it's too weak, can not be applied to an element with "display: flex"
* ".hidden" class: it has been polluted by Fomantic UI in many cases
* inline style="display: none": it's difficult to tweak
* jQuery's show/hide/toggle: it can not show/hide elements with "display: xxx !important"
only use:
-* this ".gt-hidden" class
+* this ".tw-hidden" class
* showElem/hideElem/toggleElem functions in "utils/dom.js"
*/
-.gt-hidden.gt-hidden { display: none !important; }
+.tw-hidden.tw-hidden { display: none !important; }
@media (max-width: 767.98px) {
/* double selector so it wins over .tw-flex (old .gt-df) etc */
diff --git a/web_src/css/index.css b/web_src/css/index.css
index bf568bff4d..224d3d23ab 100644
--- a/web_src/css/index.css
+++ b/web_src/css/index.css
@@ -1,19 +1,27 @@
@import "./modules/normalize.css";
@import "./modules/animations.css";
-@import "./modules/grid.css";
+
+/* fomantic replacements */
@import "./modules/button.css";
+@import "./modules/container.css";
+@import "./modules/divider.css";
+@import "./modules/header.css";
+@import "./modules/label.css";
+@import "./modules/segment.css";
+@import "./modules/grid.css";
+@import "./modules/message.css";
+@import "./modules/table.css";
+@import "./modules/card.css";
+@import "./modules/modal.css";
+
@import "./modules/select.css";
@import "./modules/tippy.css";
-@import "./modules/modal.css";
@import "./modules/breadcrumb.css";
-@import "./modules/card.css";
@import "./modules/comment.css";
@import "./modules/navbar.css";
@import "./modules/toast.css";
-@import "./modules/divider.css";
@import "./modules/svg.css";
@import "./modules/flexcontainer.css";
-@import "./modules/message.css";
@import "./shared/flex-list.css";
@import "./shared/milestone.css";
@@ -32,6 +40,7 @@
@import "./markup/content.css";
@import "./markup/codecopy.css";
@import "./markup/asciicast.css";
+@import "./markup/filepreview.css";
@import "./chroma/base.css";
@import "./codemirror/base.css";
diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css
index 5eeef078a5..430b4802d6 100644
--- a/web_src/css/markup/content.css
+++ b/web_src/css/markup/content.css
@@ -451,7 +451,8 @@
text-decoration: inherit;
}
-.markup pre > code {
+.markup pre > code,
+.markup .file-preview code {
padding: 0;
margin: 0;
font-size: 100%;
diff --git a/web_src/css/markup/filepreview.css b/web_src/css/markup/filepreview.css
new file mode 100644
index 0000000000..d2ec16ea8b
--- /dev/null
+++ b/web_src/css/markup/filepreview.css
@@ -0,0 +1,41 @@
+.markup table.file-preview {
+ margin-bottom: 0;
+}
+
+.markup table.file-preview td {
+ padding: 0 10px !important;
+ border: none !important;
+}
+
+.markup table.file-preview tr {
+ border-top: none;
+ background-color: inherit !important;
+}
+
+.markup .file-preview-box {
+ margin-bottom: 16px;
+}
+
+.markup .file-preview-box .header {
+ padding: .5rem;
+ padding-left: 1rem;
+ border: 1px solid var(--color-secondary);
+ border-bottom: none;
+ border-radius: 0.28571429rem 0.28571429rem 0 0;
+ background: var(--color-box-header);
+}
+
+.markup .file-preview-box .warning {
+ border-radius: 0;
+ margin: 0;
+ padding: .5rem .5rem .5rem 1rem;
+}
+
+.markup .file-preview-box .header > a {
+ display: block;
+}
+
+.markup .file-preview-box .table {
+ margin-top: 0;
+ border-radius: 0 0 0.28571429rem 0.28571429rem;
+}
diff --git a/web_src/css/modules/animations.css b/web_src/css/modules/animations.css
index 5bfc090773..788a4ed6ed 100644
--- a/web_src/css/modules/animations.css
+++ b/web_src/css/modules/animations.css
@@ -13,6 +13,7 @@
opacity: 0.3;
}
+.btn.is-loading > *,
.button.is-loading > * {
opacity: 0;
}
diff --git a/web_src/css/modules/button.css b/web_src/css/modules/button.css
index a06cab4465..0799ab80ec 100644
--- a/web_src/css/modules/button.css
+++ b/web_src/css/modules/button.css
@@ -63,11 +63,22 @@ It needs some tricks to tweak the left/right borders with active state */
border-right: none;
}
-.ui.buttons .button:first-child {
+.ui.buttons .button:hover {
+ border-color: var(--color-secondary-dark-2);
+}
+
+.ui.buttons .button:hover + .button {
+ border-left: 1px solid var(--color-secondary-dark-2);
+}
+
+/* TODO: these "tw-hidden" selectors are only used by "blame.tmpl" buttons: Raw/Normal View/History/Unescape, need to be refactored to a clear solution later */
+.ui.buttons .button:first-child,
+.ui.buttons .button.tw-hidden:first-child + .button {
border-left: 1px solid var(--color-light-border);
}
-.ui.buttons .button:last-child {
+.ui.buttons .button:last-child,
+.ui.buttons .button:nth-last-child(2):has(+ .button.tw-hidden) {
border-right: 1px solid var(--color-light-border);
}
@@ -107,6 +118,7 @@ It needs some tricks to tweak the left/right borders with active state */
.ui.basic.button:focus {
color: var(--color-text);
background: var(--color-hover);
+ border-color: var(--color-secondary-dark-2);
}
.ui.basic.buttons .button:active,
diff --git a/web_src/css/modules/container.css b/web_src/css/modules/container.css
new file mode 100644
index 0000000000..dc854f89d0
--- /dev/null
+++ b/web_src/css/modules/container.css
@@ -0,0 +1,78 @@
+/* based on Fomantic UI container module, with just the parts extracted that we use. If you find any
+ unused rules here after refactoring, please remove them. */
+
+.ui.container {
+ display: block;
+ max-width: 100%;
+}
+
+@media (max-width: 767.98px) {
+ .ui.ui.ui.container:not(.fluid) {
+ width: auto;
+ margin-left: 1em;
+ margin-right: 1em;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 991.98px) {
+ .ui.ui.ui.container:not(.fluid) {
+ width: 723px;
+ margin-left: auto;
+ margin-right: auto;
+ }
+}
+
+@media (min-width: 992px) and (max-width: 1199.98px) {
+ .ui.ui.ui.container:not(.fluid) {
+ width: 933px;
+ margin-left: auto;
+ margin-right: auto;
+ }
+}
+
+@media (min-width: 1200px) {
+ .ui.ui.ui.container:not(.fluid) {
+ width: 1127px;
+ margin-left: auto;
+ margin-right: auto;
+ }
+}
+
+.ui.fluid.container {
+ width: 100%;
+}
+
+.ui[class*="center aligned"].container {
+ text-align: center;
+}
+
+/* overwrite width of containers inside the main page content div (div with class "page-content") */
+.page-content .ui.ui.ui.container:not(.fluid) {
+ width: 1280px;
+ max-width: calc(100% - 64px);
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.ui.container.fluid.padded {
+ padding: 0 32px;
+}
+
+/* enable fluid page widths for medium size viewports */
+@media (min-width: 768px) and (max-width: 1200px) {
+ .page-content .ui.ui.ui.container:not(.fluid) {
+ max-width: calc(100% - 32px);
+ }
+ .ui.container.fluid.padded {
+ padding: 0 16px;
+ }
+}
+
+@media (max-width: 767.98px) {
+ .page-content .ui.ui.ui.container:not(.fluid) {
+ max-width: calc(100% - 16px);
+ }
+ .ui.container.fluid.padded {
+ padding: 0 8px;
+ }
+}
diff --git a/web_src/css/modules/grid.css b/web_src/css/modules/grid.css
index 5a80576c8a..4aaa452372 100644
--- a/web_src/css/modules/grid.css
+++ b/web_src/css/modules/grid.css
@@ -406,6 +406,15 @@
align-self: center !important;
}
+.ui[class*="left aligned"].grid > .column,
+.ui[class*="left aligned"].grid > .row > .column,
+.ui.grid > [class*="left aligned"].row > .column,
+.ui.grid > [class*="left aligned"].column.column,
+.ui.grid > .row > [class*="left aligned"].column.column {
+ text-align: left;
+ align-self: inherit;
+}
+
.ui[class*="center aligned"].grid > .column,
.ui[class*="center aligned"].grid > .row > .column,
.ui.grid > [class*="center aligned"].row > .column,
@@ -418,6 +427,15 @@
justify-content: center;
}
+.ui[class*="right aligned"].grid > .column,
+.ui[class*="right aligned"].grid > .row > .column,
+.ui.grid > [class*="right aligned"].row > .column,
+.ui.grid > [class*="right aligned"].column.column,
+.ui.grid > .row > [class*="right aligned"].column.column {
+ text-align: right;
+ align-self: inherit;
+}
+
.ui[class*="equal width"].grid > .column:not(.row),
.ui[class*="equal width"].grid > .row > .column,
.ui.grid > [class*="equal width"].row > .column {
diff --git a/web_src/css/modules/header.css b/web_src/css/modules/header.css
new file mode 100644
index 0000000000..091d536cfc
--- /dev/null
+++ b/web_src/css/modules/header.css
@@ -0,0 +1,171 @@
+/* based on Fomantic UI header module, with just the parts extracted that we use. If you find any
+ unused rules here after refactoring, please remove them. */
+
+.ui.header {
+ color: var(--color-text);
+ border: none;
+ margin: calc(2rem - 0.1428571428571429em) 0 1rem;
+ padding: 0;
+ font-family: var(--fonts-regular);
+ font-weight: var(--font-weight-medium);
+ line-height: 1.28571429;
+ text-transform: none;
+}
+
+.ui.header:first-child {
+ margin-top: -0.14285714em;
+}
+
+.ui.header:last-child {
+ margin-bottom: 0;
+}
+
+.ui.header .ui.label {
+ margin-left: 0.25rem;
+ vertical-align: middle;
+}
+
+.ui.header > .ui.label.compact {
+ margin-top: inherit;
+}
+
+.ui.header .sub.header {
+ display: block;
+ font-weight: var(--font-weight-normal);
+ padding: 0;
+ margin: 0;
+ font-size: 1rem;
+ line-height: 1.2;
+ color: var(--color-text-light-1);
+}
+
+.ui.header > i.icon {
+ display: table-cell;
+ opacity: 1;
+ font-size: 1.5em;
+ padding-top: 0;
+ vertical-align: middle;
+}
+
+.ui.header > i.icon:only-child {
+ display: inline-block;
+ padding: 0;
+ margin-right: 0.75rem;
+}
+
+.ui.header + p {
+ margin-top: 0;
+}
+
+h2.ui.header {
+ font-size: 1.71428571rem;
+}
+h2.ui.header .sub.header {
+ font-size: 1.14285714rem;
+}
+
+h4.ui.header {
+ font-size: 1.07142857rem;
+}
+h4.ui.header .sub.header {
+ font-size: 1rem;
+}
+
+.ui.sub.header {
+ padding: 0;
+ margin-bottom: 0.14285714rem;
+ font-weight: var(--font-weight-normal);
+ font-size: 0.85714286em;
+}
+
+.ui.icon.header svg {
+ width: 3em;
+ height: 3em;
+ float: none;
+ display: block;
+ line-height: var(--line-height-default);
+ padding: 0;
+ margin: 0 auto 0.5rem;
+ opacity: 1;
+}
+
+.ui.header:not(h1,h2,h3,h4,h5,h6) {
+ font-size: 1.28571429em;
+}
+
+.ui.attached.header {
+ position: relative;
+ background: var(--color-box-header);
+ padding: 0.78571429rem 1rem;
+ margin: 0 -1px;
+ border-radius: 0;
+ border: 1px solid var(--color-secondary);
+}
+
+.ui.attached:not(.top).header {
+ border-top: none;
+}
+
+.ui.top.attached.header {
+ border-radius: 0.28571429rem 0.28571429rem 0 0;
+}
+
+.ui.bottom.attached.header {
+ border-radius: 0 0 0.28571429rem 0.28571429rem;
+}
+
+.ui.attached.header:not(h1,h2,h3,h4,h5,h6) {
+ font-size: 1em;
+}
+
+/* fix misaligned right buttons on box headers */
+.ui.attached.header > .ui.right {
+ position: absolute;
+ right: 0.78571429rem;
+ top: 0;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ gap: 0.25em;
+}
+
+/* the default ".ui.attached.header > .ui.right" is only able to contain "tiny" buttons, other buttons are too large */
+.ui.attached.header > .ui.right .ui.tiny.button {
+ padding: 6px 10px;
+ font-weight: var(--font-weight-normal);
+}
+
+/* if a .top.attached.header is followed by a .segment, add some margin */
+.ui.segments + .ui.top.attached.header,
+.ui.attached.segment + .ui.top.attached.header {
+ margin-top: 1rem;
+}
+
+.ui.dividing.header {
+ border-bottom-color: var(--color-secondary);
+}
+
+.ui.dividing.header .sub.header {
+ padding-bottom: 0.21428571rem;
+}
+
+.ui.dividing.header i.icon {
+ margin-bottom: 0;
+}
+
+.ui.error.header {
+ background: var(--color-error-bg) !important;
+ color: var(--color-error-text) !important;
+ border-color: var(--color-error-border) !important;
+}
+
+.ui.warning.header {
+ background: var(--color-warning-bg) !important;
+ color: var(--color-warning-text) !important;
+ border-color: var(--color-warning-border) !important;
+}
+
+.attention-header {
+ padding: 0.5em 0.75em !important;
+ color: var(--color-text) !important;
+}
diff --git a/web_src/css/modules/label.css b/web_src/css/modules/label.css
new file mode 100644
index 0000000000..0512c5fddb
--- /dev/null
+++ b/web_src/css/modules/label.css
@@ -0,0 +1,294 @@
+/* based on Fomantic UI label module, with just the parts extracted that we use. If you find any
+ unused rules here after refactoring, please remove them. */
+
+.ui.label {
+ display: inline-flex;
+ align-items: center;
+ gap: .25rem;
+ vertical-align: middle;
+ line-height: 1;
+ background: var(--color-label-bg);
+ color: var(--color-label-text);
+ padding: 0.3em 0.5em;
+ text-transform: none;
+ font-size: 0.85714286rem;
+ font-weight: var(--font-weight-medium);
+ border: 0 solid transparent;
+ border-radius: 0.28571429rem;
+ white-space: nowrap;
+}
+
+.ui.label:first-child {
+ margin-left: 0;
+}
+.ui.label:last-child {
+ margin-right: 0;
+}
+
+a.ui.label {
+ cursor: pointer;
+}
+
+.ui.label > a {
+ cursor: pointer;
+ color: inherit;
+ opacity: 0.75;
+}
+.ui.label > a:hover {
+ opacity: 1;
+}
+
+.ui.label > img {
+ width: auto;
+ vertical-align: middle;
+ height: 2.1666em;
+}
+
+.ui.label > .color-icon {
+ margin-left: 0;
+}
+
+.ui.label > .icon {
+ width: auto;
+ margin: 0 0.75em 0 0;
+}
+
+.ui.label > .detail {
+ display: inline-block;
+ vertical-align: top;
+ font-weight: var(--font-weight-medium);
+ margin-left: 1em;
+ opacity: 0.8;
+}
+.ui.label > .detail .icon {
+ margin: 0 0.25em 0 0;
+}
+
+.ui.label > .close.icon,
+.ui.label > .delete.icon {
+ cursor: pointer;
+ font-size: 0.92857143em;
+ opacity: 0.5;
+}
+.ui.label > .close.icon:hover,
+.ui.label > .delete.icon:hover {
+ opacity: 1;
+}
+
+.ui.label.left.icon > .close.icon,
+.ui.label.left.icon > .delete.icon {
+ margin: 0 0.5em 0 0;
+}
+.ui.label:not(.icon) > .close.icon,
+.ui.label:not(.icon) > .delete.icon {
+ margin: 0 0 0 0.5em;
+}
+
+.ui.header > .ui.label {
+ margin-top: -0.29165em;
+}
+
+a.ui.label:hover {
+ background: var(--color-label-hover-bg);
+ border-color: var(--color-label-hover-bg);
+ color: var(--color-label-text);
+}
+
+.ui.label.visible:not(.dropdown) {
+ display: inline-block !important;
+}
+
+.ui.basic.label {
+ 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;
+ color: var(--color-text);
+ border-color: var(--color-light-border);
+ background: var(--color-hover);
+}
+
+.ui.ui.ui.primary.label {
+ background: var(--color-primary);
+ border-color: var(--color-primary-dark-2);
+ color: var(--color-primary-contrast);
+}
+a.ui.ui.ui.primary.label:hover {
+ background: var(--color-primary-dark-3);
+ border-color: var(--color-primary-dark-3);
+ color: var(--color-primary-contrast);
+}
+.ui.ui.ui.basic.primary.label {
+ background: transparent;
+ border-color: var(--color-primary);
+ color: var(--color-primary);
+}
+a.ui.ui.ui.basic.primary.label:hover {
+ background: var(--color-hover);
+ border-color: var(--color-primary-dark-1);
+ color: var(--color-primary-dark-1);
+}
+
+.ui.ui.ui.red.label {
+ background: var(--color-red);
+ border-color: var(--color-red);
+ color: var(--color-white);
+}
+a.ui.ui.ui.red.label:hover {
+ background: var(--color-red-dark-1);
+ border-color: var(--color-red-dark-1);
+ color: var(--color-white);
+}
+.ui.ui.ui.basic.red.label {
+ background: transparent;
+ border-color: var(--color-red);
+ color: var(--color-red);
+}
+a.ui.ui.ui.basic.red.label:hover {
+ background: transparent;
+ border-color: var(--color-red-dark-1);
+ color: var(--color-red-dark-1);
+}
+
+.ui.ui.ui.orange.label {
+ background: var(--color-orange);
+ border-color: var(--color-orange);
+ color: var(--color-white);
+}
+a.ui.ui.ui.orange.label:hover {
+ background: var(--color-orange-dark-1);
+ border-color: var(--color-orange-dark-1);
+ color: var(--color-white);
+}
+.ui.ui.ui.basic.orange.label {
+ background: transparent;
+ border-color: var(--color-orange);
+ color: var(--color-orange);
+}
+a.ui.ui.ui.basic.orange.label:hover {
+ background: transparent;
+ border-color: var(--color-orange-dark-1);
+ color: var(--color-orange-dark-1);
+}
+
+.ui.ui.ui.yellow.label {
+ background: var(--color-yellow);
+ border-color: var(--color-yellow);
+ color: var(--color-white);
+}
+a.ui.ui.ui.yellow.label:hover {
+ background: var(--color-yellow-dark-1);
+ border-color: var(--color-yellow-dark-1);
+ color: var(--color-white);
+}
+.ui.ui.ui.basic.yellow.label {
+ background: transparent;
+ border-color: var(--color-yellow);
+ color: var(--color-yellow);
+}
+a.ui.ui.ui.basic.yellow.label:hover {
+ background: transparent;
+ border-color: var(--color-yellow-dark-1);
+ color: var(--color-yellow-dark-1);
+}
+.ui.ui.ui.olive.label {
+ background: var(--color-olive);
+ border-color: var(--color-olive);
+ color: var(--color-white);
+}
+
+.ui.ui.ui.green.label {
+ background: var(--color-green);
+ border-color: var(--color-green);
+ color: var(--color-white);
+}
+a.ui.ui.ui.green.label:hover {
+ background: var(--color-green-dark-1);
+ border-color: var(--color-green-dark-1);
+ color: var(--color-white);
+}
+.ui.ui.ui.basic.green.label {
+ background: transparent;
+ border-color: var(--color-green);
+ color: var(--color-green);
+}
+a.ui.ui.ui.basic.green.label:hover {
+ background: transparent;
+ border-color: var(--color-green-dark-1);
+ color: var(--color-green-dark-1);
+}
+
+.ui.ui.ui.purple.label {
+ background: var(--color-purple);
+ border-color: var(--color-purple);
+ color: var(--color-white);
+}
+a.ui.ui.ui.purple.label:hover {
+ background: var(--color-purple-dark-1);
+ border-color: var(--color-purple-dark-1);
+ color: var(--color-white);
+}
+.ui.ui.ui.basic.purple.label {
+ background: transparent;
+ border-color: var(--color-purple);
+ color: var(--color-purple);
+}
+a.ui.ui.ui.basic.purple.label:hover {
+ background: transparent;
+ border-color: var(--color-purple-dark-1);
+ color: var(--color-purple-dark-1);
+}
+
+.ui.ui.ui.grey.label {
+ background: var(--color-label-bg);
+ border-color: var(--color-label-bg);
+ color: var(--color-label-text);
+}
+a.ui.ui.ui.grey.label:hover {
+ background: var(--color-label-hover-bg);
+ border-color: var(--color-label-hover-bg);
+ color: var(--color-white);
+}
+.ui.ui.ui.basic.grey.label {
+ background: transparent;
+ border-color: var(--color-label-bg);
+ color: var(--color-label-text);
+}
+a.ui.ui.ui.basic.grey.label:hover {
+ background: transparent;
+ border-color: var(--color-label-hover-bg);
+ color: var(--color-label-hover-bg);
+}
+
+.ui.horizontal.label {
+ margin: 0 0.5em 0 0;
+ padding: 0.4em 0.833em;
+ min-width: 3em;
+ text-align: center;
+}
+
+.ui.circular.label {
+ min-width: 2em;
+ min-height: 2em;
+ padding: 0.5em !important;
+ line-height: 1;
+ text-align: center;
+ border-radius: 500rem;
+ justify-content: center;
+}
+
+.ui.mini.label {
+ font-size: 0.64285714rem;
+}
+.ui.tiny.label {
+ font-size: 0.71428571rem;
+}
+.ui.small.label {
+ font-size: 0.78571429rem;
+}
+.ui.large.label {
+ font-size: 1rem;
+}
diff --git a/web_src/css/modules/message.css b/web_src/css/modules/message.css
index a29603cd91..c62dbddd25 100644
--- a/web_src/css/modules/message.css
+++ b/web_src/css/modules/message.css
@@ -100,3 +100,15 @@
color: var(--color-warning-text);
border-color: var(--color-warning-border);
}
+
+.ui.message > .close.icon {
+ cursor: pointer;
+ position: absolute;
+ top: 9px;
+ right: 9px;
+ opacity: .7;
+}
+
+.ui.message > .close.icon:hover {
+ opacity: 1;
+}
diff --git a/web_src/css/modules/navbar.css b/web_src/css/modules/navbar.css
index 3673e89a11..6906eb49a2 100644
--- a/web_src/css/modules/navbar.css
+++ b/web_src/css/modules/navbar.css
@@ -136,3 +136,7 @@
justify-content: center;
z-index: 1; /* prevent menu button background from overlaying icon */
}
+
+.secondary-nav {
+ background: var(--color-secondary-nav-bg) !important; /* important because of .ui.secondary.menu */
+}
diff --git a/web_src/css/modules/segment.css b/web_src/css/modules/segment.css
new file mode 100644
index 0000000000..963b2209b0
--- /dev/null
+++ b/web_src/css/modules/segment.css
@@ -0,0 +1,195 @@
+/* based on Fomantic UI segment module, with just the parts extracted that we use. If you find any
+ unused rules here after refactoring, please remove them. */
+
+.ui.segment {
+ position: relative;
+ margin: 1rem 0;
+ padding: 1em;
+ border-radius: 0.28571429rem;
+ border: 1px solid var(--color-secondary);
+ background: var(--color-box-body);
+ color: var(--color-text);
+}
+.ui.segment:first-child {
+ margin-top: 0;
+}
+.ui.segment:last-child {
+ margin-bottom: 0;
+}
+
+.ui.grid.segment {
+ margin: 1rem 0;
+ border-radius: 0.28571429rem;
+}
+
+.ui.segment.tab:last-child {
+ margin-bottom: 1rem;
+}
+
+.ui.segments {
+ flex-direction: column;
+ position: relative;
+ margin: 1rem 0;
+ border: 1px solid var(--color-secondary);
+ border-radius: 0.28571429rem;
+ background: var(--color-box-body);
+ color: var(--color-text);
+}
+.ui.segments:first-child {
+ margin-top: 0;
+}
+.ui.segments:last-child {
+ margin-bottom: 0;
+}
+
+.ui.segments > .segment {
+ top: 0;
+ bottom: 0;
+ border-radius: 0;
+ margin: 0;
+ width: auto;
+ box-shadow: none;
+ border: none;
+ border-top: 1px solid var(--color-secondary);
+}
+.ui.segments:not(.horizontal) > .segment:first-child {
+ top: 0;
+ bottom: 0;
+ border-top: none;
+ margin-top: 0;
+ margin-bottom: 0;
+ border-radius: 0.28571429rem 0.28571429rem 0 0;
+}
+
+.ui.segments:not(.horizontal) > .segment:last-child {
+ top: 0;
+ bottom: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ border-radius: 0 0 0.28571429rem 0.28571429rem;
+}
+
+.ui.segments:not(.horizontal) > .segment:only-child {
+ border-radius: 0.214285717rem;
+}
+
+.ui.segments > .ui.segments {
+ border-top: 1px solid var(--color-secondary);
+ margin: 1rem;
+}
+.ui.segments > .segments:first-child {
+ border-top: none;
+}
+.ui.segments > .segment + .segments:not(.horizontal) {
+ margin-top: 0;
+}
+
+.ui.horizontal.segments {
+ display: flex;
+ flex-direction: row;
+ background-color: transparent;
+ padding: 0;
+ margin: 1rem 0;
+ border-radius: 0.28571429rem;
+ border: 1px solid var(--color-secondary);
+}
+
+.ui.horizontal.segments > .segment {
+ margin: 0;
+ min-width: 0;
+ border-radius: 0;
+ border: none;
+ box-shadow: none;
+ border-left: 1px solid var(--color-secondary);
+}
+
+.ui.segments > .horizontal.segments:first-child {
+ border-top: none;
+}
+.ui.horizontal.segments:not(.stackable) > .segment:first-child {
+ border-left: none;
+}
+.ui.horizontal.segments > .segment:first-child {
+ border-radius: 0.28571429rem 0 0 0.28571429rem;
+}
+.ui.horizontal.segments > .segment:last-child {
+ border-radius: 0 0.28571429rem 0.28571429rem 0;
+}
+
+.ui.clearing.segment::after {
+ content: "";
+ display: block;
+ clear: both;
+}
+
+.ui[class*="left aligned"].segment {
+ text-align: left;
+}
+.ui[class*="center aligned"].segment {
+ text-align: center;
+}
+
+.ui.secondary.segment {
+ background: var(--color-secondary-bg);
+ color: var(--color-text-light);
+}
+
+.ui.attached.segment {
+ top: 0;
+ bottom: 0;
+ border-radius: 0;
+ margin: 0 -1px;
+ width: calc(100% + 2px);
+ max-width: calc(100% + 2px);
+ box-shadow: none;
+ border: 1px solid var(--color-secondary);
+ background: var(--color-box-body);
+ color: var(--color-text);
+}
+.ui.attached:not(.message) + .ui.attached.segment:not(.top) {
+ border-top: none;
+}
+
+.ui[class*="top attached"].segment {
+ bottom: 0;
+ margin-bottom: 0;
+ top: 0;
+ margin-top: 1rem;
+ border-radius: 0.28571429rem 0.28571429rem 0 0;
+}
+.ui.segment[class*="top attached"]:first-child {
+ margin-top: 0;
+}
+
+.ui.segment[class*="bottom attached"] {
+ bottom: 0;
+ margin-top: 0;
+ top: 0;
+ margin-bottom: 1rem;
+ border-radius: 0 0 0.28571429rem 0.28571429rem;
+}
+.ui.segment[class*="bottom attached"]:last-child {
+ margin-bottom: 1rem;
+}
+
+.ui.fitted.segment:not(.horizontally) {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+.ui.fitted.segment:not(.vertically) {
+ padding-left: 0;
+ padding-right: 0;
+}
+
+.ui.segments .segment,
+.ui.segment {
+ font-size: 1rem;
+}
+
+.ui.error.segment {
+ border-color: var(--color-error-border) !important;
+}
+
+.ui.warning.segment {
+ border-color: var(--color-warning-border) !important;
+}
diff --git a/web_src/css/modules/table.css b/web_src/css/modules/table.css
new file mode 100644
index 0000000000..4fb9d4214e
--- /dev/null
+++ b/web_src/css/modules/table.css
@@ -0,0 +1,385 @@
+/* based on Fomantic UI segment module, with just the parts extracted that we use. If you find any
+ unused rules here after refactoring, please remove them. */
+
+.ui.table {
+ width: 100%;
+ margin: 1em 0;
+ border: 1px solid var(--color-secondary);
+ border-radius: 0.28571429rem;
+ vertical-align: middle;
+ border-collapse: separate;
+ border-spacing: 0;
+ color: var(--color-text);
+ background: var(--color-box-body);
+ border-color: var(--color-secondary);
+ text-align: start;
+}
+
+.ui.table:first-child {
+ margin-top: 0;
+}
+.ui.table:last-child {
+ margin-bottom: 0;
+}
+.ui.table > thead,
+.ui.table > tbody {
+ text-align: inherit;
+ vertical-align: inherit;
+}
+
+.ui.table > thead > tr > th {
+ background: var(--color-box-header);
+ text-align: inherit;
+ color: var(--color-text);
+ padding: 6px 5px;
+ vertical-align: inherit;
+ font-weight: var(--font-weight-normal);
+ border-bottom: 1px solid var(--color-secondary);
+ border-left: none;
+}
+.ui.table > thead > tr > th:first-child {
+ border-left: none;
+}
+.ui.table > thead > tr:first-child > th:first-child {
+ border-radius: 0.28571429rem 0 0;
+}
+.ui.table > thead > tr:first-child > th:last-child {
+ border-radius: 0 0.28571429rem 0 0;
+}
+.ui.table > thead > tr:first-child > th:only-child {
+ border-radius: 0.28571429rem 0.28571429rem 0 0;
+}
+
+.ui.table > tfoot > tr > th,
+.ui.table > tfoot > tr > td {
+ border-top: 1px solid var(--color-secondary);
+ background: var(--color-box-body);
+ text-align: inherit;
+ color: var(--color-text);
+ padding: 0.78571429em;
+ vertical-align: inherit;
+ font-weight: var(--font-weight-normal);
+}
+.ui.table > tfoot > tr > th:first-child,
+.ui.table > tfoot > tr > td:first-child {
+ border-left: none;
+}
+.ui.table > tfoot > tr:first-child > th:first-child,
+.ui.table > tfoot > tr:first-child > td:first-child {
+ border-radius: 0 0 0 0.28571429rem;
+}
+.ui.table > tfoot > tr:first-child > th:last-child,
+.ui.table > tfoot > tr:first-child > td:last-child {
+ border-radius: 0 0 0.28571429rem;
+}
+.ui.table > tfoot > tr:first-child > th:only-child,
+.ui.table > tfoot > tr:first-child > td:only-child {
+ border-radius: 0 0 0.28571429rem 0.28571429rem;
+}
+
+.ui.table > tr > td,
+.ui.table > tbody > tr > td {
+ border-top: 1px solid var(--color-secondary-alpha-50);
+ padding: 6px 5px;
+ text-align: inherit;
+}
+.ui.table > tr:first-child > td,
+.ui.table > tbody > tr:first-child > td {
+ border-top: none;
+}
+
+.ui.table.segment {
+ padding: 0;
+}
+.ui.table.segment::after {
+ display: none;
+}
+
+@media only screen and (max-width: 767.98px) {
+ .ui.table:not(.unstackable) {
+ width: 100%;
+ padding: 0;
+ }
+ .ui.table:not(.unstackable) > thead,
+ .ui.table:not(.unstackable) > thead > tr,
+ .ui.table:not(.unstackable) > tfoot,
+ .ui.table:not(.unstackable) > tfoot > tr,
+ .ui.table:not(.unstackable) > tbody,
+ .ui.table:not(.unstackable) > tr,
+ .ui.table:not(.unstackable) > tbody > tr,
+ .ui.table:not(.unstackable) > tr > th,
+ .ui.table:not(.unstackable) > thead > tr > th,
+ .ui.table:not(.unstackable) > tbody > tr > th,
+ .ui.table:not(.unstackable) > tfoot > tr > th,
+ .ui.table:not(.unstackable) > tr > td,
+ .ui.table:not(.unstackable) > tbody > tr > td,
+ .ui.table:not(.unstackable) > tfoot > tr > td {
+ display: block !important;
+ width: auto !important;
+ }
+ .ui.table:not(.unstackable) > thead {
+ display: block;
+ }
+ .ui.table:not(.unstackable) > tfoot {
+ display: block;
+ }
+ .ui.ui.ui.ui.table:not(.unstackable) > tr,
+ .ui.ui.ui.ui.table:not(.unstackable) > thead > tr,
+ .ui.ui.ui.ui.table:not(.unstackable) > tbody > tr,
+ .ui.ui.ui.ui.table:not(.unstackable) > tfoot > tr {
+ padding-top: 1em;
+ padding-bottom: 1em;
+ }
+ .ui.ui.ui.ui.table:not(.unstackable) > tr > th,
+ .ui.ui.ui.ui.table:not(.unstackable) > thead > tr > th,
+ .ui.ui.ui.ui.table:not(.unstackable) > tbody > tr > th,
+ .ui.ui.ui.ui.table:not(.unstackable) > tfoot > tr > th,
+ .ui.ui.ui.ui.table:not(.unstackable) > tr > td,
+ .ui.ui.ui.ui.table:not(.unstackable) > tbody > tr > td,
+ .ui.ui.ui.ui.table:not(.unstackable) > tfoot > tr > td {
+ background: none;
+ border: none;
+ padding: 0.25em 0.75em;
+ }
+ .ui.table:not(.unstackable) > tr > th:first-child,
+ .ui.table:not(.unstackable) > thead > tr > th:first-child,
+ .ui.table:not(.unstackable) > tbody > tr > th:first-child,
+ .ui.table:not(.unstackable) > tfoot > tr > th:first-child,
+ .ui.table:not(.unstackable) > tr > td:first-child,
+ .ui.table:not(.unstackable) > tbody > tr > td:first-child,
+ .ui.table:not(.unstackable) > tfoot > tr > td:first-child {
+ font-weight: var(--font-weight-normal);
+ }
+}
+
+.ui.table[class*="left aligned"],
+.ui.table [class*="left aligned"] {
+ text-align: left;
+}
+
+.ui.table[class*="center aligned"],
+.ui.table [class*="center aligned"] {
+ text-align: center;
+}
+
+.ui.table[class*="right aligned"],
+.ui.table [class*="right aligned"] {
+ text-align: right;
+}
+
+.ui.table[class*="top aligned"],
+.ui.table [class*="top aligned"] {
+ vertical-align: top;
+}
+
+.ui.table[class*="middle aligned"],
+.ui.table [class*="middle aligned"] {
+ vertical-align: middle;
+}
+
+.ui.table th.collapsing,
+.ui.table td.collapsing {
+ width: 1px;
+ white-space: nowrap;
+}
+
+.ui.fixed.table {
+ table-layout: fixed;
+}
+.ui.fixed.table th,
+.ui.fixed.table td {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.ui.attached.table {
+ top: 0;
+ bottom: 0;
+ border-radius: 0;
+ margin: 0 -1px;
+ width: calc(100% + 2px);
+ max-width: calc(100% + 2px);
+ border: 1px solid var(--color-secondary);
+}
+.ui.attached + .ui.attached.table:not(.top) {
+ border-top: none;
+}
+
+.ui[class*="bottom attached"].table {
+ bottom: 0;
+ margin-top: 0;
+ top: 0;
+ margin-bottom: 1em;
+ border-radius: 0 0 0.28571429rem 0.28571429rem;
+}
+.ui[class*="bottom attached"].table:last-child {
+ margin-bottom: 0;
+}
+
+.ui.striped.table > tr:nth-child(2n),
+.ui.striped.table > tbody > tr:nth-child(2n) {
+ background: var(--color-light);
+}
+
+.ui.table[class*="single line"],
+.ui.table [class*="single line"] {
+ white-space: nowrap;
+}
+
+/* Column Width */
+.ui.table th.one.wide,
+.ui.table td.one.wide {
+ width: 6.25%;
+}
+.ui.table th.two.wide,
+.ui.table td.two.wide {
+ width: 12.5%;
+}
+.ui.table th.three.wide,
+.ui.table td.three.wide {
+ width: 18.75%;
+}
+.ui.table th.four.wide,
+.ui.table td.four.wide {
+ width: 25%;
+}
+.ui.table th.five.wide,
+.ui.table td.five.wide {
+ width: 31.25%;
+}
+.ui.table th.six.wide,
+.ui.table td.six.wide {
+ width: 37.5%;
+}
+.ui.table th.seven.wide,
+.ui.table td.seven.wide {
+ width: 43.75%;
+}
+.ui.table th.eight.wide,
+.ui.table td.eight.wide {
+ width: 50%;
+}
+.ui.table th.nine.wide,
+.ui.table td.nine.wide {
+ width: 56.25%;
+}
+.ui.table th.ten.wide,
+.ui.table td.ten.wide {
+ width: 62.5%;
+}
+.ui.table th.eleven.wide,
+.ui.table td.eleven.wide {
+ width: 68.75%;
+}
+.ui.table th.twelve.wide,
+.ui.table td.twelve.wide {
+ width: 75%;
+}
+.ui.table th.thirteen.wide,
+.ui.table td.thirteen.wide {
+ width: 81.25%;
+}
+.ui.table th.fourteen.wide,
+.ui.table td.fourteen.wide {
+ width: 87.5%;
+}
+.ui.table th.fifteen.wide,
+.ui.table td.fifteen.wide {
+ width: 93.75%;
+}
+.ui.table th.sixteen.wide,
+.ui.table td.sixteen.wide {
+ width: 100%;
+}
+
+.ui.basic.table {
+ background: transparent;
+ border: 1px solid var(--color-secondary);
+}
+.ui.basic.table > thead > tr > th,
+.ui.basic.table > tbody > tr > th,
+.ui.basic.table > tfoot > tr > th,
+.ui.basic.table > tr > th {
+ background: transparent;
+ border-left: none;
+}
+.ui.basic.table > tbody > tr {
+ border-bottom: 1px solid var(--color-secondary);
+}
+.ui.basic.table > tbody > tr > td,
+.ui.basic.table > tfoot > tr > td,
+.ui.basic.table > tr > td {
+ background: transparent;
+}
+.ui.basic.striped.table > tbody > tr:nth-child(2n) {
+ background: var(--color-light);
+}
+
+.ui[class*="very basic"].table {
+ border: none;
+}
+.ui[class*="very basic"].table:not(.striped) > tr > th:first-child,
+.ui[class*="very basic"].table:not(.striped) > thead > tr > th:first-child,
+.ui[class*="very basic"].table:not(.striped) > tbody > tr > th:first-child,
+.ui[class*="very basic"].table:not(.striped) > tfoot > tr > th:first-child,
+.ui[class*="very basic"].table:not(.striped) > tr > td:first-child,
+.ui[class*="very basic"].table:not(.striped) > tbody > tr > td:first-child,
+.ui[class*="very basic"].table:not(.striped) > tfoot > tr > td:first-child {
+ padding-left: 0;
+}
+.ui[class*="very basic"].table:not(.striped) > tr > th:last-child,
+.ui[class*="very basic"].table:not(.striped) > thead > tr > th:last-child,
+.ui[class*="very basic"].table:not(.striped) > tbody > tr > th:last-child,
+.ui[class*="very basic"].table:not(.striped) > tfoot > tr > th:last-child,
+.ui[class*="very basic"].table:not(.striped) > tr > td:last-child,
+.ui[class*="very basic"].table:not(.striped) > tbody > tr > td:last-child,
+.ui[class*="very basic"].table:not(.striped) > tfoot > tr > td:last-child {
+ padding-right: 0;
+}
+.ui[class*="very basic"].table:not(.striped) > thead > tr:first-child > th {
+ padding-top: 0;
+}
+
+.ui.celled.table > tr > th,
+.ui.celled.table > thead > tr > th,
+.ui.celled.table > tbody > tr > th,
+.ui.celled.table > tfoot > tr > th,
+.ui.celled.table > tr > td,
+.ui.celled.table > tbody > tr > td,
+.ui.celled.table > tfoot > tr > td {
+ border-left: 1px solid var(--color-secondary-alpha-50);
+}
+.ui.celled.table > tr > th:first-child,
+.ui.celled.table > thead > tr > th:first-child,
+.ui.celled.table > tbody > tr > th:first-child,
+.ui.celled.table > tfoot > tr > th:first-child,
+.ui.celled.table > tr > td:first-child,
+.ui.celled.table > tbody > tr > td:first-child,
+.ui.celled.table > tfoot > tr > td:first-child {
+ border-left: none;
+}
+
+.ui.compact.table > tr > th,
+.ui.compact.table > thead > tr > th,
+.ui.compact.table > tbody > tr > th,
+.ui.compact.table > tfoot > tr > th {
+ padding-left: 0.7em;
+ padding-right: 0.7em;
+}
+.ui.compact.table > tr > td,
+.ui.compact.table > tbody > tr > td,
+.ui.compact.table > tfoot > tr > td {
+ padding: 0.5em 0.7em;
+}
+
+/* use more horizontal padding on first and last items for visuals */
+.ui.table > thead > tr > th:first-of-type,
+.ui.table > tbody > tr > td:first-of-type,
+.ui.table > tr > td:first-of-type {
+ padding-left: 10px;
+}
+.ui.table > thead > tr > th:last-of-type,
+.ui.table > tbody > tr > td:last-of-type,
+.ui.table > tr > td:last-of-type {
+ padding-right: 10px;
+}
diff --git a/web_src/css/repo.css b/web_src/css/repo.css
index 128748621e..2257553055 100644
--- a/web_src/css/repo.css
+++ b/web_src/css/repo.css
@@ -4,16 +4,6 @@
user-select: none;
}
-.repository .navbar {
- display: flex;
- justify-content: space-between;
-}
-
-.repository .navbar .ui.label {
- margin-left: 7px;
- padding: 3px 5px;
-}
-
.repository .owner.dropdown {
min-width: 40% !important;
}
@@ -579,6 +569,10 @@
display: flex;
}
+.issue-title-buttons {
+ gap: 0.5rem;
+}
+
@media (max-width: 767.98px) {
.repository.view.issue .issue-title {
flex-direction: column;
@@ -621,7 +615,7 @@
line-height: 40px;
margin: 0;
padding-right: 0.25rem;
- min-height: 41px; /* avoid layout shift on edit */
+ min-height: var(--repo-header-issue-min-height);
}
.repository.view.issue .issue-title h1 .ui.input {
@@ -1611,7 +1605,6 @@
.repository .diff-file-box .file-body.file-code .lines-num {
text-align: right;
- color: var(--color-text-light);
width: 1%;
min-width: 50px;
}
@@ -1885,6 +1878,31 @@
border-bottom: 1px solid var(--color-warning-border);
}
+.repository .release-tag-name .ui.label.isSigned,
+.repository .release-list-title .ui.label.isSigned {
+ padding: 0 0.5em;
+ box-shadow: none;
+}
+
+.repository .release-tag-name .ui.label.isSigned .avatar,
+.repository .release-list-title .ui.label.isSigned .avatar {
+ margin-left: .5rem;
+}
+
+.repository .release-tag-name .ui.label.isSigned.isVerified,
+.repository .release-list-title .ui.label.isSigned.isVerified {
+ border: 1px solid var(--color-success-border);
+ background-color: var(--color-success-bg);
+ color: var(--color-success-text);
+}
+
+.repository .release-tag-name .ui.label.isSigned.isWarning,
+.repository .release-list-title .ui.label.isSigned.isWarning {
+ border: 1px solid var(--color-warning-border);
+ background-color: var(--color-warning-bg);
+ color: var(--color-warning-text);
+}
+
.repository .segment.reactions.dropdown .menu,
.repository .select-reaction.dropdown .menu {
right: 0 !important;
@@ -2118,12 +2136,19 @@
padding-top: 15px;
}
-.commit-header-row {
+.commit-header-row,
+.tag-signature-row {
min-height: 50px !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
+.tag-signature-row div {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+ display: inline-block !important;
+}
+
.settings.webhooks .list > .item:not(:first-child),
.settings.githooks .list > .item:not(:first-child),
.settings.actions .list > .item:not(:first-child) {
@@ -2191,7 +2216,7 @@
margin: 0 !important;
position: relative;
color: var(--color-text);
- min-height: 41px;
+ min-height: var(--repo-header-issue-min-height);
background-color: var(--color-box-header);
display: flex;
justify-content: space-between;
@@ -2443,6 +2468,7 @@ tbody.commit-list {
.author-wrapper {
max-width: 180px;
+ align-self: center;
}
/* in the commit list, messages can wrap so we can use inline */
@@ -3020,3 +3046,7 @@ tbody.commit-list {
margin-top: -1px;
border-top: 1px solid var(--color-secondary);
}
+#issue-info-popup .emoji {
+ font-size: inherit;
+ line-height: inherit;
+}
diff --git a/web_src/css/repo/header.css b/web_src/css/repo/header.css
index 0a2c0c82ad..55e704ed10 100644
--- a/web_src/css/repo/header.css
+++ b/web_src/css/repo/header.css
@@ -1,4 +1,8 @@
-.header-wrapper .fork-flag {
+.repository .secondary-nav {
+ padding-top: 12px;
+}
+
+.repository .secondary-nav .fork-flag {
margin-top: 0.5rem;
font-size: 12px;
}
@@ -8,6 +12,7 @@
flex-flow: row wrap;
justify-content: space-between;
gap: 0.5rem;
+ margin-bottom: 4px;
}
.repo-header .flex-item {
@@ -62,8 +67,3 @@
.repo-buttons .ui.labeled.button.disabled > .button {
pointer-events: none !important;
}
-
-.repository .header-wrapper {
- padding-top: 12px;
- background-color: var(--color-header-wrapper);
-}
diff --git a/web_src/css/repo/issue-card.css b/web_src/css/repo/issue-card.css
index 5a70de47c2..b9368df4f6 100644
--- a/web_src/css/repo/issue-card.css
+++ b/web_src/css/repo/issue-card.css
@@ -19,3 +19,7 @@
font-size: 14px;
margin-left: 4px;
}
+
+.issue-card.sortable-chosen .issue-card-title {
+ cursor: inherit;
+}
diff --git a/web_src/css/repo/issue-list.css b/web_src/css/repo/issue-list.css
index 6c3755fa49..e46ffeb4f0 100644
--- a/web_src/css/repo/issue-list.css
+++ b/web_src/css/repo/issue-list.css
@@ -16,6 +16,14 @@
flex-wrap: wrap;
}
+.issue-list-new.button {
+ margin-right: 0;
+}
+
+.list-header-issues {
+ min-height: var(--repo-header-issue-min-height);
+}
+
@media (max-width: 767.98px) {
.issue-list-navbar {
order: 0;
diff --git a/web_src/css/repo/linebutton.css b/web_src/css/repo/linebutton.css
index 79be5a7a9e..d32899a06b 100644
--- a/web_src/css/repo/linebutton.css
+++ b/web_src/css/repo/linebutton.css
@@ -1,20 +1,19 @@
-.code-view .lines-num:hover {
+.code-view .lines-num:hover,
+.file-preview .lines-num:hover {
color: var(--color-text-dark) !important;
}
.code-line-button {
- background-color: var(--color-menu);
- color: var(--color-text-light);
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
- padding: 1px 10px;
+ padding: 1px 4px !important;
position: absolute;
font-family: var(--fonts-regular);
left: 0;
- transform: translateX(-50%);
+ transform: translateX(calc(-50% + 6px));
cursor: pointer;
}
.code-line-button:hover {
- color: var(--color-primary);
+ background: var(--color-secondary) !important;
}
diff --git a/web_src/css/review.css b/web_src/css/review.css
index d5b675a650..a198edf6c5 100644
--- a/web_src/css/review.css
+++ b/web_src/css/review.css
@@ -96,9 +96,6 @@
}
@media (max-width: 767.98px) {
- .comment-code-cloud .comments .comment {
- display: flex;
- }
.comment-code-cloud .comments .comment .comment-header-right.actions .ui.basic.label {
display: none;
}
diff --git a/web_src/css/shared/flex-list.css b/web_src/css/shared/flex-list.css
index e1ad6e7f97..6217b45300 100644
--- a/web_src/css/shared/flex-list.css
+++ b/web_src/css/shared/flex-list.css
@@ -86,7 +86,7 @@
border-top: 1px solid var(--color-secondary);
}
-/* Fomantic UI segment has default "padding: 1em", so here it removes the padding-top and padding-bottom accordingly (there might also be some `gt-hidden` siblings).
+/* Fomantic UI segment has default "padding: 1em", so here it removes the padding-top and padding-bottom accordingly (there might also be some `tw-hidden` siblings).
Developers could also use "flex-space-fitted" class to remove the first item's padding-top and the last item's padding-bottom */
.flex-list.flex-space-fitted > .flex-item:first-child,
.ui.segment > .flex-list > .flex-item:first-child {
diff --git a/web_src/css/themes/theme-forgejo-dark.css b/web_src/css/themes/theme-forgejo-dark.css
index f27db7ac3b..0533344a8c 100644
--- a/web_src/css/themes/theme-forgejo-dark.css
+++ b/web_src/css/themes/theme-forgejo-dark.css
@@ -205,7 +205,7 @@
--color-menu: var(--steel-700);
--color-card: var(--steel-700);
--color-markup-table-row: #ffffff06;
- --color-markup-code-block: var(--steel-800);
+ --color-markup-code-block: var(--steel-850);
--color-button: var(--steel-600);
--color-code-bg: var(--steel-750);
--color-code-sidebar-bg: var(--steel-600);
@@ -226,9 +226,9 @@
--color-nav-bg: var(--steel-900);
--color-nav-hover-bg: var(--steel-600);
--color-label-text: #fff;
- --color-label-bg: #393939;
- --color-label-hover-bg: #5f5f5f;
- --color-label-active-bg: #4c4c4c;
+ --color-label-bg: #cacaca5b;
+ --color-label-hover-bg: #cacacaa0;
+ --color-label-active-bg: #cacacaff;
--color-accent: var(--color-primary-light-1);
--color-small-accent: var(--color-primary-light-5);
--color-active-line: var(--color-primary-alpha-20);
diff --git a/web_src/css/themes/theme-gitea-dark.css b/web_src/css/themes/theme-gitea-dark.css
index 9cf8907e45..626590ca54 100644
--- a/web_src/css/themes/theme-gitea-dark.css
+++ b/web_src/css/themes/theme-gitea-dark.css
@@ -183,7 +183,7 @@
--color-body: #1c1f25;
--color-box-header: #1a1d1f;
--color-box-body: #14171a;
- --color-box-body-highlight: #121517;
+ --color-box-body-highlight: #1c2227;
--color-text-dark: #f8f8f9;
--color-text: #d1d5d8;
--color-text-light: #bdc3c7;
@@ -197,7 +197,6 @@
--color-input-toggle-background: #2e353b;
--color-input-border: var(--color-secondary);
--color-input-border-hover: var(--color-secondary-dark-1);
- --color-header-wrapper: #181c20;
--color-light: #00001728;
--color-light-mimic-enabled: rgba(0, 0, 0, calc(40 / 255 * 222 / 255 / var(--opacity-disabled)));
--color-light-border: #e8e8ff28;
@@ -208,11 +207,10 @@
--color-markup-table-row: #e8e8ff06;
--color-markup-code-block: #e8e8ff16;
--color-button: #151a1e;
- --color-code-bg: #191d20;
- --color-code-sidebar-bg: #1b1f22;
+ --color-code-bg: #14171a;
--color-shadow: #00001758;
- --color-secondary-bg: #2f3135;
- --color-expand-button: #414348;
+ --color-secondary-bg: #2f3138;
+ --color-expand-button: #2b353e;
--color-placeholder-text: var(--color-text-light-3);
--color-editor-line-highlight: var(--color-primary-light-5);
--color-project-board-bg: var(--color-secondary-light-2);
@@ -227,13 +225,15 @@
--color-nav-bg: #16191c;
--color-nav-hover-bg: var(--color-secondary-light-1);
--color-nav-text: var(--color-text);
+ --color-secondary-nav-bg: #181c20;
--color-label-text: var(--color-text);
--color-label-bg: #73828e4b;
--color-label-hover-bg: #73828ea0;
--color-label-active-bg: #73828eff;
--color-accent: var(--color-primary-light-1);
--color-small-accent: var(--color-primary-light-5);
- --color-active-line: #534d1b;
+ --color-highlight-fg: #87651e;
+ --color-highlight-bg: #352c1c;
--color-overlay-backdrop: #080808c0;
accent-color: var(--color-accent);
color-scheme: dark;
diff --git a/web_src/css/themes/theme-gitea-light.css b/web_src/css/themes/theme-gitea-light.css
index 2ac83eefed..f6913fbe22 100644
--- a/web_src/css/themes/theme-gitea-light.css
+++ b/web_src/css/themes/theme-gitea-light.css
@@ -183,7 +183,7 @@
--color-body: #ffffff;
--color-box-header: #f1f3f5;
--color-box-body: #ffffff;
- --color-box-body-highlight: #f4faff;
+ --color-box-body-highlight: #ecf5fd;
--color-text-dark: #01050a;
--color-text: #181c21;
--color-text-light: #30363b;
@@ -197,7 +197,6 @@
--color-input-toggle-background: #d0d7de;
--color-input-border: var(--color-secondary);
--color-input-border-hover: var(--color-secondary-dark-1);
- --color-header-wrapper: #f9fafb;
--color-light: #00001706;
--color-light-mimic-enabled: rgba(0, 0, 0, calc(6 / 255 * 222 / 255 / var(--opacity-disabled)));
--color-light-border: #0000171d;
@@ -209,10 +208,9 @@
--color-markup-code-block: #00001710;
--color-button: #f8f9fb;
--color-code-bg: #fafdff;
- --color-code-sidebar-bg: #f2f5f8;
--color-shadow: #00001726;
--color-secondary-bg: #f2f5f8;
- --color-expand-button: #d8efff;
+ --color-expand-button: #cfe8fa;
--color-placeholder-text: var(--color-text-light-3);
--color-editor-line-highlight: var(--color-primary-light-6);
--color-project-board-bg: var(--color-secondary-light-4);
@@ -227,13 +225,15 @@
--color-nav-bg: #f6f7fa;
--color-nav-hover-bg: var(--color-secondary-light-1);
--color-nav-text: var(--color-text);
+ --color-secondary-nav-bg: #f9fafb;
--color-label-text: var(--color-text);
--color-label-bg: #949da64b;
--color-label-hover-bg: #949da6a0;
--color-label-active-bg: #949da6ff;
--color-accent: var(--color-primary-light-1);
--color-small-accent: var(--color-primary-light-6);
- --color-active-line: #fffbdd;
+ --color-highlight-fg: #eed200;
+ --color-highlight-bg: #fffbdd;
--color-overlay-backdrop: #080808c0;
accent-color: var(--color-accent);
color-scheme: light;
diff --git a/web_src/css/user.css b/web_src/css/user.css
index ece7d9b2cc..33ffa1eabc 100644
--- a/web_src/css/user.css
+++ b/web_src/css/user.css
@@ -120,13 +120,15 @@
#readme_profile {
padding: 1em 2em;
- border-radius: var(--border-radius);
- background: var(--color-card);
+ background: var(--color-box-body);
border: 1px solid var(--color-secondary);
+ border-radius: var(--border-radius);
}
-#notification_div {
- padding-top: 15px;
+#profile-avatar-card {
+ background: var(--color-box-body);
+ border: 1px solid var(--color-secondary);
+ border-radius: var(--border-radius);
}
#notification_table {
diff --git a/web_src/fomantic/build/semantic.css b/web_src/fomantic/build/semantic.css
index 099bb94c39..21c41a6161 100644
--- a/web_src/fomantic/build/semantic.css
+++ b/web_src/fomantic/build/semantic.css
@@ -3036,163 +3036,6 @@
Site Overrides
*******************************/
/*!
- * # Fomantic-UI - Container
- * http://github.com/fomantic/Fomantic-UI/
- *
- *
- * Released under the MIT license
- * http://opensource.org/licenses/MIT
- *
- */
-
-/*******************************
- Container
-*******************************/
-
-/* All Sizes */
-
-.ui.container {
- display: block;
- max-width: 100%;
-}
-
-/* Mobile */
-
-@media only screen and (max-width: 767.98px) {
- .ui.ui.ui.container:not(.fluid) {
- width: auto;
- margin-left: 1em;
- margin-right: 1em;
- }
-
- .ui.ui.ui.grid.container {
- width: auto;
- }
-
- .ui.ui.ui.relaxed.grid.container {
- width: auto;
- }
-
- .ui.ui.ui.very.relaxed.grid.container {
- width: auto;
- }
-}
-
-/* Tablet */
-
-@media only screen and (min-width: 768px) and (max-width: 991.98px) {
- .ui.ui.ui.container:not(.fluid) {
- width: 723px;
- margin-left: auto;
- margin-right: auto;
- }
-
- .ui.ui.ui.grid.container {
- width: calc(723px + 2rem);
- }
-
- .ui.ui.ui.relaxed.grid.container {
- width: calc(723px + 3rem);
- }
-
- .ui.ui.ui.very.relaxed.grid.container {
- width: calc(723px + 5rem);
- }
-}
-
-/* Small Monitor */
-
-@media only screen and (min-width: 992px) and (max-width: 1199.98px) {
- .ui.ui.ui.container:not(.fluid) {
- width: 933px;
- margin-left: auto;
- margin-right: auto;
- }
-
- .ui.ui.ui.grid.container {
- width: calc(933px + 2rem);
- }
-
- .ui.ui.ui.relaxed.grid.container {
- width: calc(933px + 3rem);
- }
-
- .ui.ui.ui.very.relaxed.grid.container {
- width: calc(933px + 5rem);
- }
-}
-
-/* Large Monitor */
-
-@media only screen and (min-width: 1200px) {
- .ui.ui.ui.container:not(.fluid) {
- width: 1127px;
- margin-left: auto;
- margin-right: auto;
- }
-
- .ui.ui.ui.grid.container {
- width: calc(1127px + 2rem);
- }
-
- .ui.ui.ui.relaxed.grid.container {
- width: calc(1127px + 3rem);
- }
-
- .ui.ui.ui.very.relaxed.grid.container {
- width: calc(1127px + 5rem);
- }
-}
-
-/*******************************
- Types
-*******************************/
-
-/* Text Container */
-
-.ui.text.container {
- font-family: var(--fonts-regular);
- max-width: 700px;
- line-height: 1.5;
- font-size: 1.14285714rem;
-}
-
-/* Fluid */
-
-.ui.fluid.container {
- width: 100%;
-}
-
-/*******************************
- Variations
-*******************************/
-
-.ui[class*="left aligned"].container {
- text-align: left;
-}
-
-.ui[class*="center aligned"].container {
- text-align: center;
-}
-
-.ui[class*="right aligned"].container {
- text-align: right;
-}
-
-.ui.justified.container {
- text-align: justify;
- -webkit-hyphens: auto;
- hyphens: auto;
-}
-
-/*******************************
- Theme Overrides
-*******************************/
-
-/*******************************
- Site Overrides
-*******************************/
-/*!
* # Fomantic-UI - Dimmer
* http://github.com/fomantic/Fomantic-UI/
*
@@ -7344,741 +7187,6 @@ select.ui.dropdown {
Site Overrides
*******************************/
/*!
- * # Fomantic-UI - Header
- * http://github.com/fomantic/Fomantic-UI/
- *
- *
- * Released under the MIT license
- * http://opensource.org/licenses/MIT
- *
- */
-
-/*******************************
- Header
-*******************************/
-
-/* Standard */
-
-.ui.header {
- border: none;
- margin: calc(2rem - 0.1428571428571429em) 0 1rem;
- padding: 0 0;
- font-family: var(--fonts-regular);
- font-weight: 500;
- line-height: 1.28571429em;
- text-transform: none;
- color: rgba(0, 0, 0, 0.87);
-}
-
-.ui.header:first-child {
- margin-top: -0.14285714em;
-}
-
-.ui.header:last-child {
- margin-bottom: 0;
-}
-
-/*--------------
- Sub Header
- ---------------*/
-
-.ui.header .sub.header {
- display: block;
- font-weight: normal;
- padding: 0;
- margin: 0;
- font-size: 1rem;
- line-height: 1.2em;
- color: rgba(0, 0, 0, 0.6);
-}
-
-/*--------------
- Icon
----------------*/
-
-.ui.header > i.icon {
- display: table-cell;
- opacity: 1;
- font-size: 1.5em;
- padding-top: 0;
- vertical-align: middle;
-}
-
-/* With Text Node */
-
-.ui.header > i.icon:only-child {
- display: inline-block;
- padding: 0;
- margin-right: 0.75rem;
-}
-
-/*-------------------
- Image
---------------------*/
-
-.ui.header > .image:not(.icon),
-.ui.header > img {
- display: inline-block;
- margin-top: 0.14285714em;
- width: 2.5em;
- height: auto;
- vertical-align: middle;
-}
-
-.ui.header > .image:not(.icon):only-child,
-.ui.header > img:only-child {
- margin-right: 0.75rem;
-}
-
-/*--------------
- Content
----------------*/
-
-.ui.header .content {
- display: inline-block;
- vertical-align: top;
-}
-
-/* After Image */
-
-.ui.header > img + .content,
-.ui.header > .image + .content {
- padding-left: 0.75rem;
- vertical-align: middle;
-}
-
-/* After Icon */
-
-.ui.header > i.icon + .content {
- padding-left: 0.75rem;
- display: table-cell;
- vertical-align: middle;
-}
-
-/*--------------
- Loose Coupling
----------------*/
-
-.ui.header .ui.label {
- font-size: '';
- margin-left: 0.5rem;
- vertical-align: middle;
-}
-
-/* Positioning */
-
-.ui.header + p {
- margin-top: 0;
-}
-
-/*******************************
- Types
-*******************************/
-
-/*--------------
- Page
----------------*/
-
-h1.ui.header {
- font-size: 2rem;
-}
-
-h1.ui.header .sub.header {
- font-size: 1.14285714rem;
-}
-
-h2.ui.header {
- font-size: 1.71428571rem;
-}
-
-h2.ui.header .sub.header {
- font-size: 1.14285714rem;
-}
-
-h3.ui.header {
- font-size: 1.28571429rem;
-}
-
-h3.ui.header .sub.header {
- font-size: 1rem;
-}
-
-h4.ui.header {
- font-size: 1.07142857rem;
-}
-
-h4.ui.header .sub.header {
- font-size: 1rem;
-}
-
-h5.ui.header {
- font-size: 1rem;
-}
-
-h5.ui.header .sub.header {
- font-size: 0.92857143rem;
-}
-
-h6.ui.header {
- font-size: 0.85714286rem;
-}
-
-h6.ui.header .sub.header {
- font-size: 0.92857143rem;
-}
-
-/*--------------
- Content Heading
----------------*/
-
-.ui.mini.header {
- font-size: 0.85714286em;
-}
-
-.ui.mini.header .sub.header {
- font-size: 0.92857143rem;
-}
-
-.ui.mini.sub.header {
- font-size: 0.78571429em;
-}
-
-.ui.tiny.header {
- font-size: 1em;
-}
-
-.ui.tiny.header .sub.header {
- font-size: 0.92857143rem;
-}
-
-.ui.tiny.sub.header {
- font-size: 0.78571429em;
-}
-
-.ui.small.header {
- font-size: 1.07142857em;
-}
-
-.ui.small.header .sub.header {
- font-size: 1rem;
-}
-
-.ui.small.sub.header {
- font-size: 0.78571429em;
-}
-
-.ui.large.header {
- font-size: 1.71428571em;
-}
-
-.ui.large.header .sub.header {
- font-size: 1.14285714rem;
-}
-
-.ui.large.sub.header {
- font-size: 0.92857143em;
-}
-
-.ui.big.header {
- font-size: 1.85714286em;
-}
-
-.ui.big.header .sub.header {
- font-size: 1.14285714rem;
-}
-
-.ui.big.sub.header {
- font-size: 1em;
-}
-
-.ui.huge.header {
- font-size: 2em;
- min-height: 1em;
-}
-
-.ui.huge.header .sub.header {
- font-size: 1.14285714rem;
-}
-
-.ui.huge.sub.header {
- font-size: 1em;
-}
-
-.ui.massive.header {
- font-size: 2.28571429em;
- min-height: 1em;
-}
-
-.ui.massive.header .sub.header {
- font-size: 1.42857143rem;
-}
-
-.ui.massive.sub.header {
- font-size: 1.14285714em;
-}
-
-/*--------------
- Sub Heading
- ---------------*/
-
-.ui.sub.header {
- padding: 0;
- margin-bottom: 0.14285714rem;
- font-weight: 500;
- font-size: 0.85714286em;
- text-transform: uppercase;
- color: '';
-}
-
-/*-------------------
- Icon
- --------------------*/
-
-.ui.icon.header {
- display: inline-block;
- text-align: center;
- margin: 2rem 0 1rem;
-}
-
-.ui.icon.header:after {
- content: '';
- display: block;
- height: 0;
- clear: both;
- visibility: hidden;
-}
-
-.ui.icon.header:first-child {
- margin-top: 0;
-}
-
-.ui.icon.header > i.icon {
- float: none;
- display: block;
- width: auto;
- height: auto;
- line-height: 1;
- padding: 0;
- font-size: 3em;
- margin: 0 auto 0.5rem;
- opacity: 1;
-}
-
-.ui.icon.header .corner.icon {
- font-size: calc(3em * 0.45);
-}
-
-.ui.icon.header .content {
- display: block;
- padding: 0;
-}
-
-.ui.icon.header > i.circular.icon {
- font-size: 2em;
-}
-
-.ui.icon.header > i.square.icon {
- font-size: 2em;
-}
-
-.ui.block.icon.header > i.icon {
- margin-bottom: 0;
-}
-
-.ui.icon.header.aligned {
- margin-left: auto;
- margin-right: auto;
- display: block;
-}
-
-/*******************************
- States
-*******************************/
-
-.ui.disabled.header {
- opacity: var(--opacity-disabled);
-}
-
-/*******************************
- Variations
-*******************************/
-
-/*-------------------
- Colors
---------------------*/
-
-.ui.primary.header {
- color: #2185D0;
-}
-
-a.ui.primary.header:hover {
- color: #1678c2;
-}
-
-.ui.primary.dividing.header {
- border-bottom: 2px solid #2185D0;
-}
-
-.ui.secondary.header {
- color: #1B1C1D;
-}
-
-a.ui.secondary.header:hover {
- color: #27292a;
-}
-
-.ui.secondary.dividing.header {
- border-bottom: 2px solid #1B1C1D;
-}
-
-.ui.red.header {
- color: #DB2828;
-}
-
-a.ui.red.header:hover {
- color: #d01919;
-}
-
-.ui.red.dividing.header {
- border-bottom: 2px solid #DB2828;
-}
-
-.ui.orange.header {
- color: #F2711C;
-}
-
-a.ui.orange.header:hover {
- color: #f26202;
-}
-
-.ui.orange.dividing.header {
- border-bottom: 2px solid #F2711C;
-}
-
-.ui.yellow.header {
- color: #FBBD08;
-}
-
-a.ui.yellow.header:hover {
- color: #eaae00;
-}
-
-.ui.yellow.dividing.header {
- border-bottom: 2px solid #FBBD08;
-}
-
-.ui.olive.header {
- color: #B5CC18;
-}
-
-a.ui.olive.header:hover {
- color: #a7bd0d;
-}
-
-.ui.olive.dividing.header {
- border-bottom: 2px solid #B5CC18;
-}
-
-.ui.green.header {
- color: #21BA45;
-}
-
-a.ui.green.header:hover {
- color: #16ab39;
-}
-
-.ui.green.dividing.header {
- border-bottom: 2px solid #21BA45;
-}
-
-.ui.teal.header {
- color: #00B5AD;
-}
-
-a.ui.teal.header:hover {
- color: #009c95;
-}
-
-.ui.teal.dividing.header {
- border-bottom: 2px solid #00B5AD;
-}
-
-.ui.blue.header {
- color: #2185D0;
-}
-
-a.ui.blue.header:hover {
- color: #1678c2;
-}
-
-.ui.blue.dividing.header {
- border-bottom: 2px solid #2185D0;
-}
-
-.ui.violet.header {
- color: #6435C9;
-}
-
-a.ui.violet.header:hover {
- color: #5829bb;
-}
-
-.ui.violet.dividing.header {
- border-bottom: 2px solid #6435C9;
-}
-
-.ui.purple.header {
- color: #A333C8;
-}
-
-a.ui.purple.header:hover {
- color: #9627ba;
-}
-
-.ui.purple.dividing.header {
- border-bottom: 2px solid #A333C8;
-}
-
-.ui.pink.header {
- color: #E03997;
-}
-
-a.ui.pink.header:hover {
- color: #e61a8d;
-}
-
-.ui.pink.dividing.header {
- border-bottom: 2px solid #E03997;
-}
-
-.ui.brown.header {
- color: #A5673F;
-}
-
-a.ui.brown.header:hover {
- color: #975b33;
-}
-
-.ui.brown.dividing.header {
- border-bottom: 2px solid #A5673F;
-}
-
-.ui.grey.header {
- color: #767676;
-}
-
-a.ui.grey.header:hover {
- color: #838383;
-}
-
-.ui.grey.dividing.header {
- border-bottom: 2px solid #767676;
-}
-
-.ui.black.header {
- color: #1B1C1D;
-}
-
-a.ui.black.header:hover {
- color: #27292a;
-}
-
-.ui.black.dividing.header {
- border-bottom: 2px solid #1B1C1D;
-}
-
-/*-------------------
- Aligned
- --------------------*/
-
-.ui.left.aligned.header {
- text-align: left;
-}
-
-.ui.right.aligned.header {
- text-align: right;
-}
-
-.ui.centered.header,
-.ui.center.aligned.header {
- text-align: center;
-}
-
-.ui.justified.header {
- text-align: justify;
-}
-
-.ui.justified.header:after {
- display: inline-block;
- content: '';
- width: 100%;
-}
-
-/*-------------------
- Floated
- --------------------*/
-
-.ui.floated.header,
-.ui[class*="left floated"].header {
- float: left;
- margin-top: 0;
- margin-right: 0.5em;
-}
-
-.ui[class*="right floated"].header {
- float: right;
- margin-top: 0;
- margin-left: 0.5em;
-}
-
-/*-------------------
- Fitted
- --------------------*/
-
-.ui.fitted.header {
- padding: 0;
-}
-
-/*-------------------
- Dividing
- --------------------*/
-
-.ui.dividing.header {
- padding-bottom: 0.21428571rem;
- border-bottom: 1px solid rgba(34, 36, 38, 0.15);
-}
-
-.ui.dividing.header .sub.header {
- padding-bottom: 0.21428571rem;
-}
-
-.ui.dividing.header i.icon {
- margin-bottom: 0;
-}
-
-/*-------------------
- Block
- --------------------*/
-
-.ui.block.header {
- background: #F3F4F5;
- padding: 0.78571429rem 1rem;
- box-shadow: none;
- border: 1px solid #D4D4D5;
- border-radius: 0.28571429rem;
-}
-
-.ui.block.header:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) {
- font-size: 1rem;
-}
-
-.ui.mini.block.header {
- font-size: 0.78571429rem;
-}
-
-.ui.tiny.block.header {
- font-size: 0.85714286rem;
-}
-
-.ui.small.block.header {
- font-size: 0.92857143rem;
-}
-
-.ui.large.block.header {
- font-size: 1.14285714rem;
-}
-
-.ui.big.block.header {
- font-size: 1.28571429rem;
-}
-
-.ui.huge.block.header {
- font-size: 1.42857143rem;
-}
-
-.ui.massive.block.header {
- font-size: 1.71428571rem;
-}
-
-/*-------------------
- Attached
- --------------------*/
-
-.ui.attached.header {
- background: #FFFFFF;
- padding: 0.78571429rem 1rem;
- margin: 0 -1px 0 -1px;
- box-shadow: none;
- border: 1px solid #D4D4D5;
- border-radius: 0;
-}
-
-.ui.attached.block.header {
- background: #F3F4F5;
-}
-
-.ui.attached:not(.top).header {
- border-top: none;
-}
-
-.ui.top.attached.header {
- border-radius: 0.28571429rem 0.28571429rem 0 0;
-}
-
-.ui.bottom.attached.header {
- border-radius: 0 0 0.28571429rem 0.28571429rem;
-}
-
-/* Attached Sizes */
-
-.ui.attached.header:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) {
- font-size: 1em;
-}
-
-.ui.mini.attached.header {
- font-size: 0.78571429em;
-}
-
-.ui.tiny.attached.header {
- font-size: 0.85714286em;
-}
-
-.ui.small.attached.header {
- font-size: 0.92857143em;
-}
-
-.ui.large.attached.header {
- font-size: 1.14285714em;
-}
-
-.ui.big.attached.header {
- font-size: 1.28571429em;
-}
-
-.ui.huge.attached.header {
- font-size: 1.42857143em;
-}
-
-.ui.massive.attached.header {
- font-size: 1.71428571em;
-}
-
-/*-------------------
- Sizing
---------------------*/
-
-.ui.header:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) {
- font-size: 1.28571429em;
-}
-
-/*******************************
- Theme Overrides
-*******************************/
-
-/*******************************
- Site Overrides
-*******************************/
-/*!
* # Fomantic-UI - Input
* http://github.com/fomantic/Fomantic-UI/
*
@@ -8823,1120 +7931,6 @@ a.ui.black.header:hover {
Site Overrides
*******************************/
/*!
- * # Fomantic-UI - Label
- * http://github.com/fomantic/Fomantic-UI/
- *
- *
- * Released under the MIT license
- * http://opensource.org/licenses/MIT
- *
- */
-
-/*******************************
- Label
-*******************************/
-
-.ui.label {
- display: inline-block;
- line-height: 1;
- vertical-align: baseline;
- margin: 0 0.14285714em;
- background-color: #E8E8E8;
- background-image: none;
- padding: 0.5833em 0.833em;
- color: rgba(0, 0, 0, 0.6);
- text-transform: none;
- font-weight: 500;
- border: 0 solid transparent;
- border-radius: 0.28571429rem;
- transition: background 0.1s ease;
-}
-
-.ui.label:first-child {
- margin-left: 0;
-}
-
-.ui.label:last-child {
- margin-right: 0;
-}
-
-/* Link */
-
-a.ui.label {
- cursor: pointer;
-}
-
-/* Inside Link */
-
-.ui.label > a {
- cursor: pointer;
- color: inherit;
- opacity: 0.5;
- transition: 0.1s opacity ease;
-}
-
-.ui.label > a:hover {
- opacity: 1;
-}
-
-/* Image */
-
-.ui.label > img {
- width: auto !important;
- vertical-align: middle;
- height: 2.1666em;
-}
-
-/* Icon */
-
-.ui.left.icon.label > .icon,
-.ui.label > .icon {
- width: auto;
- margin: 0 0.75em 0 0;
-}
-
-/* Detail */
-
-.ui.label > .detail {
- display: inline-block;
- vertical-align: top;
- font-weight: 500;
- margin-left: 1em;
- opacity: 0.8;
-}
-
-.ui.label > .detail .icon {
- margin: 0 0.25em 0 0;
-}
-
-/* Removable label */
-
-.ui.label > .close.icon,
-.ui.label > .delete.icon {
- cursor: pointer;
- font-size: 0.92857143em;
- opacity: 0.5;
- transition: background 0.1s ease;
-}
-
-.ui.label > .close.icon:hover,
-.ui.label > .delete.icon:hover {
- opacity: 1;
-}
-
-/* Backward compatible positioning */
-
-.ui.label.left.icon > .close.icon,
-.ui.label.left.icon > .delete.icon {
- margin: 0 0.5em 0 0;
-}
-
-.ui.label:not(.icon) > .close.icon,
-.ui.label:not(.icon) > .delete.icon {
- margin: 0 0 0 0.5em;
-}
-
-/* Label for only an icon */
-
-.ui.icon.label > .icon {
- margin: 0 auto;
-}
-
-/* Right Side Icon */
-
-.ui.right.icon.label > .icon {
- margin: 0 0 0 0.75em;
-}
-
-/*-------------------
- Group
---------------------*/
-
-.ui.labels > .label {
- margin: 0 0.5em 0.5em 0;
-}
-
-/*-------------------
- Coupling
---------------------*/
-
-.ui.header > .ui.label {
- margin-top: -0.29165em;
-}
-
-/* Remove border radius on attached segment */
-
-.ui.attached.segment > .ui.top.left.attached.label,
-.ui.bottom.attached.segment > .ui.top.left.attached.label {
- border-top-left-radius: 0;
-}
-
-.ui.attached.segment > .ui.top.right.attached.label,
-.ui.bottom.attached.segment > .ui.top.right.attached.label {
- border-top-right-radius: 0;
-}
-
-.ui.top.attached.segment > .ui.bottom.left.attached.label {
- border-bottom-left-radius: 0;
-}
-
-.ui.top.attached.segment > .ui.bottom.right.attached.label {
- border-bottom-right-radius: 0;
-}
-
-/* Padding on next content after a label */
-
-.ui.top.attached.label ~ .ui.bottom.attached.label + :not(.attached),
-.ui.top.attached.label + :not(.attached) {
- margin-top: 2rem !important;
-}
-
-.ui.bottom.attached.label ~ :last-child:not(.attached) {
- margin-top: 0;
- margin-bottom: 2rem !important;
-}
-
-.ui.segment:not(.basic) > .ui.top.attached.label {
- margin-top: -1px;
-}
-
-.ui.segment:not(.basic) > .ui.bottom.attached.label {
- margin-bottom: -1px;
-}
-
-.ui.segment:not(.basic) > .ui.attached.label:not(.right) {
- margin-left: -1px;
-}
-
-.ui.segment:not(.basic) > .ui.right.attached.label {
- margin-right: -1px;
-}
-
-.ui.segment:not(.basic) > .ui.attached.label:not(.left):not(.right) {
- width: calc(100% + 2px);
-}
-
-/*******************************
- Types
-*******************************/
-
-/*-------------------
- Attached
- --------------------*/
-
-.ui[class*="top attached"].label,
-.ui.attached.label {
- width: 100%;
- position: absolute;
- margin: 0;
- top: 0;
- left: 0;
- padding: 0.75em 1em;
- border-radius: 0.21428571rem 0.21428571rem 0 0;
-}
-
-.ui[class*="bottom attached"].label {
- top: auto;
- bottom: 0;
- border-radius: 0 0 0.21428571rem 0.21428571rem;
-}
-
-.ui[class*="top left attached"].label {
- width: auto;
- margin-top: 0;
- border-radius: 0.21428571rem 0 0.28571429rem 0;
-}
-
-.ui[class*="top right attached"].label {
- width: auto;
- left: auto;
- right: 0;
- border-radius: 0 0.21428571rem 0 0.28571429rem;
-}
-
-.ui[class*="bottom left attached"].label {
- width: auto;
- top: auto;
- bottom: 0;
- border-radius: 0 0.28571429rem 0 0.21428571rem;
-}
-
-.ui[class*="bottom right attached"].label {
- top: auto;
- bottom: 0;
- left: auto;
- right: 0;
- width: auto;
- border-radius: 0.28571429rem 0 0.21428571rem 0;
-}
-
-/*******************************
- States
-*******************************/
-
-/*-------------------
- Disabled
---------------------*/
-
-.ui.label.disabled {
- opacity: 0.5;
-}
-
-/*-------------------
- Hover
---------------------*/
-
-.ui.labels a.label:hover,
-a.ui.label:hover {
- background-color: #E0E0E0;
- border-color: #E0E0E0;
- background-image: none;
- color: rgba(0, 0, 0, 0.8);
-}
-
-.ui.labels a.label:hover:before,
-a.ui.label:hover:before {
- color: rgba(0, 0, 0, 0.8);
-}
-
-/*-------------------
- Active
---------------------*/
-
-.ui.active.label {
- background-color: #D0D0D0;
- border-color: #D0D0D0;
- background-image: none;
- color: rgba(0, 0, 0, 0.95);
-}
-
-.ui.active.label:before {
- background-color: #D0D0D0;
- background-image: none;
- color: rgba(0, 0, 0, 0.95);
-}
-
-/*-------------------
- Active Hover
---------------------*/
-
-.ui.labels a.active.label:hover,
-a.ui.active.label:hover {
- background-color: #C8C8C8;
- border-color: #C8C8C8;
- background-image: none;
- color: rgba(0, 0, 0, 0.95);
-}
-
-.ui.labels a.active.label:hover:before,
-a.ui.active.label:hover:before {
- background-color: #C8C8C8;
- background-image: none;
- color: rgba(0, 0, 0, 0.95);
-}
-
-/*-------------------
- Visible
---------------------*/
-
-.ui.labels.visible .label,
-.ui.label.visible:not(.dropdown) {
- display: inline-block !important;
-}
-
-/*-------------------
- Hidden
---------------------*/
-
-.ui.labels.hidden .label,
-.ui.label.hidden {
- display: none !important;
-}
-
-/*******************************
- Variations
-*******************************/
-
-/*-------------------
- Basic
- --------------------*/
-
-.ui.basic.labels .label,
-.ui.basic.label {
- background: none #FFFFFF;
- border: 1px solid rgba(34, 36, 38, 0.15);
- color: rgba(0, 0, 0, 0.87);
- box-shadow: none;
- padding-top: calc(0.5833em - 1px);
- padding-bottom: calc(0.5833em - 1px);
- padding-right: calc(0.833em - 1px);
-}
-
-.ui.basic.labels:not(.tag):not(.image):not(.ribbon) .label,
-.ui.basic.label:not(.tag):not(.image):not(.ribbon) {
- padding-left: calc(0.833em - 1px);
-}
-
-/* Link */
-
-.ui.basic.labels a.label:hover,
-a.ui.basic.label:hover {
- text-decoration: none;
- background: none #FFFFFF;
- color: #1e70bf;
- box-shadow: none;
-}
-
-/* Pointing */
-
-.ui.basic.pointing.label:before {
- border-color: inherit;
-}
-
-/*-------------------
- Fluid
- --------------------*/
-
-.ui.label.fluid,
-.ui.fluid.labels > .label {
- width: 100%;
- box-sizing: border-box;
-}
-
-/*-------------------
- Colors
---------------------*/
-
-.ui.primary.labels .label,
-.ui.ui.ui.primary.label {
- background-color: #2185D0;
- border-color: #2185D0;
- color: rgba(255, 255, 255, 0.9);
-}
-
-/* Link */
-
-.ui.primary.labels a.label:hover,
-a.ui.ui.ui.primary.label:hover {
- background-color: #1678c2;
- border-color: #1678c2;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .primary.label,
-.ui.ui.ui.basic.primary.label {
- background: none #FFFFFF;
- border-color: #2185D0;
- color: #2185D0;
-}
-
-.ui.basic.labels a.primary.label:hover,
-a.ui.ui.ui.basic.primary.label:hover {
- background: none #FFFFFF;
- border-color: #1678c2;
- color: #1678c2;
-}
-
-.ui.secondary.labels .label,
-.ui.ui.ui.secondary.label {
- background-color: #1B1C1D;
- border-color: #1B1C1D;
- color: rgba(255, 255, 255, 0.9);
-}
-
-/* Link */
-
-.ui.secondary.labels a.label:hover,
-a.ui.ui.ui.secondary.label:hover {
- background-color: #27292a;
- border-color: #27292a;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .secondary.label,
-.ui.ui.ui.basic.secondary.label {
- background: none #FFFFFF;
- border-color: #1B1C1D;
- color: #1B1C1D;
-}
-
-.ui.basic.labels a.secondary.label:hover,
-a.ui.ui.ui.basic.secondary.label:hover {
- background: none #FFFFFF;
- border-color: #27292a;
- color: #27292a;
-}
-
-.ui.red.labels .label,
-.ui.ui.ui.red.label {
- background-color: #DB2828;
- border-color: #DB2828;
- color: #FFFFFF;
-}
-
-/* Link */
-
-.ui.red.labels a.label:hover,
-a.ui.ui.ui.red.label:hover {
- background-color: #d01919;
- border-color: #d01919;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .red.label,
-.ui.ui.ui.basic.red.label {
- background: none #FFFFFF;
- border-color: #DB2828;
- color: #DB2828;
-}
-
-.ui.basic.labels a.red.label:hover,
-a.ui.ui.ui.basic.red.label:hover {
- background: none #FFFFFF;
- border-color: #d01919;
- color: #d01919;
-}
-
-.ui.orange.labels .label,
-.ui.ui.ui.orange.label {
- background-color: #F2711C;
- border-color: #F2711C;
- color: #FFFFFF;
-}
-
-/* Link */
-
-.ui.orange.labels a.label:hover,
-a.ui.ui.ui.orange.label:hover {
- background-color: #f26202;
- border-color: #f26202;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .orange.label,
-.ui.ui.ui.basic.orange.label {
- background: none #FFFFFF;
- border-color: #F2711C;
- color: #F2711C;
-}
-
-.ui.basic.labels a.orange.label:hover,
-a.ui.ui.ui.basic.orange.label:hover {
- background: none #FFFFFF;
- border-color: #f26202;
- color: #f26202;
-}
-
-.ui.yellow.labels .label,
-.ui.ui.ui.yellow.label {
- background-color: #FBBD08;
- border-color: #FBBD08;
- color: #FFFFFF;
-}
-
-/* Link */
-
-.ui.yellow.labels a.label:hover,
-a.ui.ui.ui.yellow.label:hover {
- background-color: #eaae00;
- border-color: #eaae00;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .yellow.label,
-.ui.ui.ui.basic.yellow.label {
- background: none #FFFFFF;
- border-color: #FBBD08;
- color: #FBBD08;
-}
-
-.ui.basic.labels a.yellow.label:hover,
-a.ui.ui.ui.basic.yellow.label:hover {
- background: none #FFFFFF;
- border-color: #eaae00;
- color: #eaae00;
-}
-
-.ui.olive.labels .label,
-.ui.ui.ui.olive.label {
- background-color: #B5CC18;
- border-color: #B5CC18;
- color: #FFFFFF;
-}
-
-/* Link */
-
-.ui.olive.labels a.label:hover,
-a.ui.ui.ui.olive.label:hover {
- background-color: #a7bd0d;
- border-color: #a7bd0d;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .olive.label,
-.ui.ui.ui.basic.olive.label {
- background: none #FFFFFF;
- border-color: #B5CC18;
- color: #B5CC18;
-}
-
-.ui.basic.labels a.olive.label:hover,
-a.ui.ui.ui.basic.olive.label:hover {
- background: none #FFFFFF;
- border-color: #a7bd0d;
- color: #a7bd0d;
-}
-
-.ui.green.labels .label,
-.ui.ui.ui.green.label {
- background-color: #21BA45;
- border-color: #21BA45;
- color: #FFFFFF;
-}
-
-/* Link */
-
-.ui.green.labels a.label:hover,
-a.ui.ui.ui.green.label:hover {
- background-color: #16ab39;
- border-color: #16ab39;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .green.label,
-.ui.ui.ui.basic.green.label {
- background: none #FFFFFF;
- border-color: #21BA45;
- color: #21BA45;
-}
-
-.ui.basic.labels a.green.label:hover,
-a.ui.ui.ui.basic.green.label:hover {
- background: none #FFFFFF;
- border-color: #16ab39;
- color: #16ab39;
-}
-
-.ui.teal.labels .label,
-.ui.ui.ui.teal.label {
- background-color: #00B5AD;
- border-color: #00B5AD;
- color: #FFFFFF;
-}
-
-/* Link */
-
-.ui.teal.labels a.label:hover,
-a.ui.ui.ui.teal.label:hover {
- background-color: #009c95;
- border-color: #009c95;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .teal.label,
-.ui.ui.ui.basic.teal.label {
- background: none #FFFFFF;
- border-color: #00B5AD;
- color: #00B5AD;
-}
-
-.ui.basic.labels a.teal.label:hover,
-a.ui.ui.ui.basic.teal.label:hover {
- background: none #FFFFFF;
- border-color: #009c95;
- color: #009c95;
-}
-
-.ui.blue.labels .label,
-.ui.ui.ui.blue.label {
- background-color: #2185D0;
- border-color: #2185D0;
- color: #FFFFFF;
-}
-
-/* Link */
-
-.ui.blue.labels a.label:hover,
-a.ui.ui.ui.blue.label:hover {
- background-color: #1678c2;
- border-color: #1678c2;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .blue.label,
-.ui.ui.ui.basic.blue.label {
- background: none #FFFFFF;
- border-color: #2185D0;
- color: #2185D0;
-}
-
-.ui.basic.labels a.blue.label:hover,
-a.ui.ui.ui.basic.blue.label:hover {
- background: none #FFFFFF;
- border-color: #1678c2;
- color: #1678c2;
-}
-
-.ui.violet.labels .label,
-.ui.ui.ui.violet.label {
- background-color: #6435C9;
- border-color: #6435C9;
- color: #FFFFFF;
-}
-
-/* Link */
-
-.ui.violet.labels a.label:hover,
-a.ui.ui.ui.violet.label:hover {
- background-color: #5829bb;
- border-color: #5829bb;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .violet.label,
-.ui.ui.ui.basic.violet.label {
- background: none #FFFFFF;
- border-color: #6435C9;
- color: #6435C9;
-}
-
-.ui.basic.labels a.violet.label:hover,
-a.ui.ui.ui.basic.violet.label:hover {
- background: none #FFFFFF;
- border-color: #5829bb;
- color: #5829bb;
-}
-
-.ui.purple.labels .label,
-.ui.ui.ui.purple.label {
- background-color: #A333C8;
- border-color: #A333C8;
- color: #FFFFFF;
-}
-
-/* Link */
-
-.ui.purple.labels a.label:hover,
-a.ui.ui.ui.purple.label:hover {
- background-color: #9627ba;
- border-color: #9627ba;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .purple.label,
-.ui.ui.ui.basic.purple.label {
- background: none #FFFFFF;
- border-color: #A333C8;
- color: #A333C8;
-}
-
-.ui.basic.labels a.purple.label:hover,
-a.ui.ui.ui.basic.purple.label:hover {
- background: none #FFFFFF;
- border-color: #9627ba;
- color: #9627ba;
-}
-
-.ui.pink.labels .label,
-.ui.ui.ui.pink.label {
- background-color: #E03997;
- border-color: #E03997;
- color: #FFFFFF;
-}
-
-/* Link */
-
-.ui.pink.labels a.label:hover,
-a.ui.ui.ui.pink.label:hover {
- background-color: #e61a8d;
- border-color: #e61a8d;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .pink.label,
-.ui.ui.ui.basic.pink.label {
- background: none #FFFFFF;
- border-color: #E03997;
- color: #E03997;
-}
-
-.ui.basic.labels a.pink.label:hover,
-a.ui.ui.ui.basic.pink.label:hover {
- background: none #FFFFFF;
- border-color: #e61a8d;
- color: #e61a8d;
-}
-
-.ui.brown.labels .label,
-.ui.ui.ui.brown.label {
- background-color: #A5673F;
- border-color: #A5673F;
- color: #FFFFFF;
-}
-
-/* Link */
-
-.ui.brown.labels a.label:hover,
-a.ui.ui.ui.brown.label:hover {
- background-color: #975b33;
- border-color: #975b33;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .brown.label,
-.ui.ui.ui.basic.brown.label {
- background: none #FFFFFF;
- border-color: #A5673F;
- color: #A5673F;
-}
-
-.ui.basic.labels a.brown.label:hover,
-a.ui.ui.ui.basic.brown.label:hover {
- background: none #FFFFFF;
- border-color: #975b33;
- color: #975b33;
-}
-
-.ui.grey.labels .label,
-.ui.ui.ui.grey.label {
- background-color: #767676;
- border-color: #767676;
- color: #FFFFFF;
-}
-
-/* Link */
-
-.ui.grey.labels a.label:hover,
-a.ui.ui.ui.grey.label:hover {
- background-color: #838383;
- border-color: #838383;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .grey.label,
-.ui.ui.ui.basic.grey.label {
- background: none #FFFFFF;
- border-color: #767676;
- color: #767676;
-}
-
-.ui.basic.labels a.grey.label:hover,
-a.ui.ui.ui.basic.grey.label:hover {
- background: none #FFFFFF;
- border-color: #838383;
- color: #838383;
-}
-
-.ui.black.labels .label,
-.ui.ui.ui.black.label {
- background-color: #1B1C1D;
- border-color: #1B1C1D;
- color: #FFFFFF;
-}
-
-/* Link */
-
-.ui.black.labels a.label:hover,
-a.ui.ui.ui.black.label:hover {
- background-color: #27292a;
- border-color: #27292a;
- color: #FFFFFF;
-}
-
-/* Basic */
-
-.ui.basic.labels .black.label,
-.ui.ui.ui.basic.black.label {
- background: none #FFFFFF;
- border-color: #1B1C1D;
- color: #1B1C1D;
-}
-
-.ui.basic.labels a.black.label:hover,
-a.ui.ui.ui.basic.black.label:hover {
- background: none #FFFFFF;
- border-color: #27292a;
- color: #27292a;
-}
-
-/*-------------------
- Horizontal
---------------------*/
-
-.ui.horizontal.labels .label,
-.ui.horizontal.label {
- margin: 0 0.5em 0 0;
- padding: 0.4em 0.833em;
- min-width: 3em;
- text-align: center;
-}
-
-/*-------------------
- Circular
- --------------------*/
-
-.ui.circular.labels .label,
-.ui.circular.label {
- min-width: 2em;
- min-height: 2em;
- padding: 0.5em !important;
- line-height: 1em;
- text-align: center;
- border-radius: 500rem;
-}
-
-.ui.empty.circular.labels .label,
-.ui.empty.circular.label {
- min-width: 0;
- min-height: 0;
- overflow: hidden;
- width: 0.5em;
- height: 0.5em;
- vertical-align: baseline;
-}
-
-/*-------------------
- Pointing
- --------------------*/
-
-.ui.pointing.label {
- position: relative;
-}
-
-.ui.attached.pointing.label {
- position: absolute;
-}
-
-.ui.pointing.label:before {
- background-color: inherit;
- background-image: inherit;
- border-width: 0;
- border-style: solid;
- border-color: inherit;
-}
-
-/* Arrow */
-
-.ui.pointing.label:before {
- position: absolute;
- content: '';
- transform: rotate(45deg);
- background-image: none;
- z-index: 2;
- width: 0.6666em;
- height: 0.6666em;
- transition: none;
-}
-
-/*--- Above ---*/
-
-.ui.pointing.label,
-.ui[class*="pointing above"].label {
- margin-top: 1em;
-}
-
-.ui.pointing.label:before,
-.ui[class*="pointing above"].label:before {
- border-width: 1px 0 0 1px;
- transform: translateX(-50%) translateY(-50%) rotate(45deg);
- top: 0;
- left: 50%;
-}
-
-/*--- Below ---*/
-
-.ui[class*="bottom pointing"].label,
-.ui[class*="pointing below"].label {
- margin-top: 0;
- margin-bottom: 1em;
-}
-
-.ui[class*="bottom pointing"].label:before,
-.ui[class*="pointing below"].label:before {
- border-width: 0 1px 1px 0;
- top: auto;
- right: auto;
- transform: translateX(-50%) translateY(-50%) rotate(45deg);
- top: 100%;
- left: 50%;
-}
-
-/*--- Left ---*/
-
-.ui[class*="left pointing"].label {
- margin-top: 0;
- margin-left: 0.6666em;
-}
-
-.ui[class*="left pointing"].label:before {
- border-width: 0 0 1px 1px;
- transform: translateX(-50%) translateY(-50%) rotate(45deg);
- bottom: auto;
- right: auto;
- top: 50%;
- left: 0;
-}
-
-/*--- Right ---*/
-
-.ui[class*="right pointing"].label {
- margin-top: 0;
- margin-right: 0.6666em;
-}
-
-.ui[class*="right pointing"].label:before {
- border-width: 1px 1px 0 0;
- transform: translateX(50%) translateY(-50%) rotate(45deg);
- top: 50%;
- right: 0;
- bottom: auto;
- left: auto;
-}
-
-/* Basic Pointing */
-
-/*--- Above ---*/
-
-.ui.basic.pointing.label:before,
-.ui.basic[class*="pointing above"].label:before {
- margin-top: -1px;
-}
-
-/*--- Below ---*/
-
-.ui.basic[class*="bottom pointing"].label:before,
-.ui.basic[class*="pointing below"].label:before {
- bottom: auto;
- top: 100%;
- margin-top: 1px;
-}
-
-/*--- Left ---*/
-
-.ui.basic[class*="left pointing"].label:before {
- top: 50%;
- left: -1px;
-}
-
-/*--- Right ---*/
-
-.ui.basic[class*="right pointing"].label:before {
- top: 50%;
- right: -1px;
-}
-
-/*------------------
- Floating Label
- -------------------*/
-
-.ui.floating.label {
- position: absolute;
- z-index: 100;
- top: -1em;
- right: 0;
- white-space: nowrap;
- transform: translateX(50%);
-}
-
-.ui.right.aligned.floating.label {
- transform: translateX(1.2em);
-}
-
-.ui.left.floating.label {
- left: 0;
- right: auto;
- transform: translateX(-50%);
-}
-
-.ui.left.aligned.floating.label {
- transform: translateX(-1.2em);
-}
-
-.ui.bottom.floating.label {
- top: auto;
- bottom: -1em;
-}
-
-/*-------------------
- Sizes
---------------------*/
-
-.ui.labels .label,
-.ui.label {
- font-size: 0.85714286rem;
-}
-
-.ui.mini.labels .label,
-.ui.mini.label {
- font-size: 0.64285714rem;
-}
-
-.ui.tiny.labels .label,
-.ui.tiny.label {
- font-size: 0.71428571rem;
-}
-
-.ui.small.labels .label,
-.ui.small.label {
- font-size: 0.78571429rem;
-}
-
-.ui.large.labels .label,
-.ui.large.label {
- font-size: 1rem;
-}
-
-.ui.big.labels .label,
-.ui.big.label {
- font-size: 1.28571429rem;
-}
-
-.ui.huge.labels .label,
-.ui.huge.label {
- font-size: 1.42857143rem;
-}
-
-.ui.massive.labels .label,
-.ui.massive.label {
- font-size: 1.71428571rem;
-}
-
-/*******************************
- Theme Overrides
-*******************************/
-
-/*******************************
- Site Overrides
-*******************************/
-/*!
* # Fomantic-UI - List
* http://github.com/fomantic/Fomantic-UI/
*
@@ -14288,674 +12282,6 @@ Floated Menu / Item
Site Overrides
*******************************/
/*!
- * # Fomantic-UI - Segment
- * http://github.com/fomantic/Fomantic-UI/
- *
- *
- * Released under the MIT license
- * http://opensource.org/licenses/MIT
- *
- */
-
-/*******************************
- Segment
-*******************************/
-
-.ui.segment {
- position: relative;
- background: #FFFFFF;
- box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15);
- margin: 1rem 0;
- padding: 1em 1em;
- border-radius: 0.28571429rem;
- border: 1px solid rgba(34, 36, 38, 0.15);
-}
-
-.ui.segment:first-child {
- margin-top: 0;
-}
-
-.ui.segment:last-child {
- margin-bottom: 0;
-}
-
-/* Vertical */
-
-.ui.vertical.segment {
- margin: 0;
- padding-left: 0;
- padding-right: 0;
- background: none transparent;
- border-radius: 0;
- box-shadow: none;
- border: none;
- border-bottom: 1px solid rgba(34, 36, 38, 0.15);
-}
-
-.ui.vertical.segment:last-child {
- border-bottom: none;
-}
-
-/*-------------------
- Loose Coupling
---------------------*/
-
-/* Label */
-
-.ui[class*="bottom attached"].segment > [class*="top attached"].label {
- border-top-left-radius: 0;
- border-top-right-radius: 0;
-}
-
-.ui[class*="top attached"].segment > [class*="bottom attached"].label {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
-}
-
-.ui.attached.segment:not(.top):not(.bottom) > [class*="top attached"].label {
- border-top-left-radius: 0;
- border-top-right-radius: 0;
-}
-
-.ui.attached.segment:not(.top):not(.bottom) > [class*="bottom attached"].label {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
-}
-
-/* Grid */
-
-.ui.page.grid.segment,
-.ui.grid > .row > .ui.segment.column,
-.ui.grid > .ui.segment.column {
- padding-top: 2em;
- padding-bottom: 2em;
-}
-
-.ui.grid.segment {
- margin: 1rem 0;
- border-radius: 0.28571429rem;
-}
-
-/* Table */
-
-.ui.basic.table.segment {
- background: #FFFFFF;
- border: 1px solid rgba(34, 36, 38, 0.15);
- box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15);
-}
-
-.ui[class*="very basic"].table.segment {
- padding: 1em 1em;
-}
-
-/* Tab */
-
-.ui.segment.tab:last-child {
- margin-bottom: 1rem;
-}
-
-/*******************************
- Types
-*******************************/
-
-/*-------------------
- Placeholder
- --------------------*/
-
-.ui.placeholder.segment {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: stretch;
- max-width: initial;
- animation: none;
- overflow: visible;
- padding: 1em 1em;
- min-height: 18rem;
- background: #F9FAFB;
- border-color: rgba(34, 36, 38, 0.15);
- box-shadow: 0 2px 25px 0 rgba(34, 36, 38, 0.05) inset;
-}
-
-.ui.placeholder.segment .button,
-.ui.placeholder.segment textarea {
- display: block;
-}
-
-.ui.placeholder.segment .field,
-.ui.placeholder.segment textarea,
-.ui.placeholder.segment > .ui.input,
-.ui.placeholder.segment .button {
- max-width: 15rem;
- margin-left: auto;
- margin-right: auto;
-}
-
-.ui.placeholder.segment .column .button,
-.ui.placeholder.segment .column .field,
-.ui.placeholder.segment .column textarea,
-.ui.placeholder.segment .column > .ui.input {
- max-width: 15rem;
- margin-left: auto;
- margin-right: auto;
-}
-
-.ui.placeholder.segment > .inline {
- align-self: center;
-}
-
-.ui.placeholder.segment > .inline > .button {
- display: inline-block;
- width: auto;
- margin: 0 0.35714286rem 0 0;
-}
-
-.ui.placeholder.segment > .inline > .button:last-child {
- margin-right: 0;
-}
-
-/*-------------------
- Padded
- --------------------*/
-
-.ui.padded.segment {
- padding: 1.5em;
-}
-
-.ui[class*="very padded"].segment {
- padding: 3em;
-}
-
-/* Padded vertical */
-
-.ui.padded.segment.vertical.segment,
-.ui[class*="very padded"].vertical.segment {
- padding-left: 0;
- padding-right: 0;
-}
-
-/*-------------------
- Compact
- --------------------*/
-
-.ui.compact.segment {
- display: table;
-}
-
-/* Compact Group */
-
-.ui.compact.segments {
- display: inline-flex;
-}
-
-.ui.compact.segments .segment,
-.ui.segments .compact.segment {
- display: block;
- flex: 0 1 auto;
-}
-
-/*-------------------
- Circular
- --------------------*/
-
-.ui.circular.segment {
- display: table-cell;
- padding: 2em;
- text-align: center;
- vertical-align: middle;
- border-radius: 500em;
-}
-
-/*-------------------
- Raised
- --------------------*/
-
-.ui.raised.raised.segments,
-.ui.raised.raised.segment {
- box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15);
-}
-
-/*******************************
- Groups
- *******************************/
-
-/* Group */
-
-.ui.segments {
- flex-direction: column;
- position: relative;
- margin: 1rem 0;
- border: 1px solid rgba(34, 36, 38, 0.15);
- box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15);
- border-radius: 0.28571429rem;
-}
-
-.ui.segments:first-child {
- margin-top: 0;
-}
-
-.ui.segments:last-child {
- margin-bottom: 0;
-}
-
-/* Nested Segment */
-
-.ui.segments > .segment {
- top: 0;
- bottom: 0;
- border-radius: 0;
- margin: 0;
- width: auto;
- box-shadow: none;
- border: none;
- border-top: 1px solid rgba(34, 36, 38, 0.15);
-}
-
-.ui.segments:not(.horizontal) > .segment:first-child {
- top: 0;
- bottom: 0;
- border-top: none;
- margin-top: 0;
- margin-bottom: 0;
- border-radius: 0.28571429rem 0.28571429rem 0 0;
-}
-
-/* Bottom */
-
-.ui.segments:not(.horizontal) > .segment:last-child {
- top: 0;
- bottom: 0;
- margin-top: 0;
- margin-bottom: 0;
- box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), none;
- border-radius: 0 0 0.28571429rem 0.28571429rem;
-}
-
-/* Only */
-
-.ui.segments:not(.horizontal) > .segment:only-child {
- border-radius: 0.28571429rem;
-}
-
-/* Nested Group */
-
-.ui.segments > .ui.segments {
- border-top: 1px solid rgba(34, 36, 38, 0.15);
- margin: 1rem 1rem;
-}
-
-.ui.segments > .segments:first-child {
- border-top: none;
-}
-
-.ui.segments > .segment + .segments:not(.horizontal) {
- margin-top: 0;
-}
-
-/* Horizontal Group */
-
-.ui.horizontal.segments {
- display: flex;
- flex-direction: row;
- background-color: transparent;
- padding: 0;
- box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15);
- margin: 1rem 0;
- border-radius: 0.28571429rem;
- border: 1px solid rgba(34, 36, 38, 0.15);
-}
-
-.ui.stackable.horizontal.segments {
- flex-wrap: wrap;
-}
-
-/* Nested Horizontal Group */
-
-.ui.segments > .horizontal.segments {
- margin: 0;
- background-color: transparent;
- border-radius: 0;
- border: none;
- box-shadow: none;
- border-top: 1px solid rgba(34, 36, 38, 0.15);
-}
-
-/* Horizontal Segment */
-
-.ui.horizontal.segments:not(.compact) > .segment:not(.compact) {
- flex: 1 1 auto;
- -ms-flex: 1 1 0;
- /* Solves #2550 MS Flex */
-}
-
-.ui.horizontal.segments > .segment {
- margin: 0;
- min-width: 0;
- border-radius: 0;
- border: none;
- box-shadow: none;
- border-left: 1px solid rgba(34, 36, 38, 0.15);
-}
-
-/* Border Fixes */
-
-.ui.segments > .horizontal.segments:first-child {
- border-top: none;
-}
-
-.ui.horizontal.segments:not(.stackable) > .segment:first-child {
- border-left: none;
-}
-
-.ui.horizontal.segments > .segment:first-child {
- border-radius: 0.28571429rem 0 0 0.28571429rem;
-}
-
-.ui.horizontal.segments > .segment:last-child {
- border-radius: 0 0.28571429rem 0.28571429rem 0;
-}
-
-/*******************************
- States
-*******************************/
-
-/*--------------
- Disabled
- ---------------*/
-
-.ui.disabled.segment {
- opacity: var(--opacity-disabled);
- color: rgba(40, 40, 40, 0.3);
-}
-
-/*--------------
- Loading
- ---------------*/
-
-.ui.loading.segment {
- position: relative;
- cursor: default;
- pointer-events: none;
- text-shadow: none !important;
- transition: all 0s linear;
-}
-
-.ui.loading.segment:before {
- position: absolute;
- content: '';
- top: 0;
- left: 0;
- background: rgba(255, 255, 255, 0.8);
- width: 100%;
- height: 100%;
- border-radius: 0.28571429rem;
- z-index: 100;
-}
-
-.ui.loading.segment:after {
- position: absolute;
- content: '';
- top: 50%;
- left: 50%;
- margin: -1.5em 0 0 -1.5em;
- width: 3em;
- height: 3em;
- animation: loader 0.6s infinite linear;
- border: 0.2em solid #767676;
- border-radius: 500rem;
- box-shadow: 0 0 0 1px transparent;
- visibility: visible;
- z-index: 101;
-}
-
-/*******************************
- Variations
-*******************************/
-
-/*-------------------
- Basic
- --------------------*/
-
-.ui.basic.segment,
-.ui.segments .ui.basic.segment,
-.ui.basic.segments {
- background: none transparent;
- box-shadow: none;
- border: none;
- border-radius: 0;
-}
-
-/*-------------------
- Clearing
- --------------------*/
-
-.ui.clearing.segment:after {
- content: "";
- display: block;
- clear: both;
-}
-
-/*-------------------
- Colors
---------------------*/
-
-.ui.red.segment.segment.segment.segment.segment:not(.inverted) {
- border-top: 2px solid #DB2828;
-}
-
-.ui.orange.segment.segment.segment.segment.segment:not(.inverted) {
- border-top: 2px solid #F2711C;
-}
-
-.ui.yellow.segment.segment.segment.segment.segment:not(.inverted) {
- border-top: 2px solid #FBBD08;
-}
-
-.ui.olive.segment.segment.segment.segment.segment:not(.inverted) {
- border-top: 2px solid #B5CC18;
-}
-
-.ui.green.segment.segment.segment.segment.segment:not(.inverted) {
- border-top: 2px solid #21BA45;
-}
-
-.ui.teal.segment.segment.segment.segment.segment:not(.inverted) {
- border-top: 2px solid #00B5AD;
-}
-
-.ui.blue.segment.segment.segment.segment.segment:not(.inverted) {
- border-top: 2px solid #2185D0;
-}
-
-.ui.violet.segment.segment.segment.segment.segment:not(.inverted) {
- border-top: 2px solid #6435C9;
-}
-
-.ui.purple.segment.segment.segment.segment.segment:not(.inverted) {
- border-top: 2px solid #A333C8;
-}
-
-.ui.pink.segment.segment.segment.segment.segment:not(.inverted) {
- border-top: 2px solid #E03997;
-}
-
-.ui.brown.segment.segment.segment.segment.segment:not(.inverted) {
- border-top: 2px solid #A5673F;
-}
-
-.ui.grey.segment.segment.segment.segment.segment:not(.inverted) {
- border-top: 2px solid #767676;
-}
-
-.ui.black.segment.segment.segment.segment.segment:not(.inverted) {
- border-top: 2px solid #1B1C1D;
-}
-
-/*-------------------
- Aligned
- --------------------*/
-
-.ui[class*="left aligned"].segment {
- text-align: left;
-}
-
-.ui[class*="right aligned"].segment {
- text-align: right;
-}
-
-.ui[class*="center aligned"].segment {
- text-align: center;
-}
-
-/*-------------------
- Floated
- --------------------*/
-
-.ui.floated.segment,
-.ui[class*="left floated"].segment {
- float: left;
- margin-right: 1em;
-}
-
-.ui[class*="right floated"].segment {
- float: right;
- margin-left: 1em;
-}
-
-/*-------------------
- Emphasis
---------------------*/
-
-/* Secondary */
-
-.ui.secondary.segment {
- background: #F3F4F5;
- color: rgba(0, 0, 0, 0.6);
-}
-
-/* Tertiary */
-
-.ui.tertiary.segment {
- background: #DCDDDE;
- color: rgba(0, 0, 0, 0.6);
-}
-
-/*-------------------
- Attached
- --------------------*/
-
-/* Middle */
-
-.ui.attached.segment {
- top: 0;
- bottom: 0;
- border-radius: 0;
- margin: 0 -1px;
- width: calc(100% + 2px);
- max-width: calc(100% + 2px);
- box-shadow: none;
- border: 1px solid #D4D4D5;
-}
-
-.ui.attached:not(.message) + .ui.attached.segment:not(.top) {
- border-top: none;
-}
-
-/* Top */
-
-.ui[class*="top attached"].segment {
- bottom: 0;
- margin-bottom: 0;
- top: 0;
- margin-top: 1rem;
- border-radius: 0.28571429rem 0.28571429rem 0 0;
-}
-
-.ui.segment[class*="top attached"]:first-child {
- margin-top: 0;
-}
-
-/* Bottom */
-
-.ui.segment[class*="bottom attached"] {
- bottom: 0;
- margin-top: 0;
- top: 0;
- margin-bottom: 1rem;
- box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15), none;
- border-radius: 0 0 0.28571429rem 0.28571429rem;
-}
-
-.ui.segment[class*="bottom attached"]:last-child {
- margin-bottom: 1rem;
-}
-
-/*--------------
- Fitted
- ---------------*/
-
-.ui.fitted.segment:not(.horizontally) {
- padding-top: 0;
- padding-bottom: 0;
-}
-
-.ui.fitted.segment:not(.vertically) {
- padding-left: 0;
- padding-right: 0;
-}
-
-/*-------------------
- Size
---------------------*/
-
-.ui.segments .segment,
-.ui.segment {
- font-size: 1rem;
-}
-
-.ui.mini.segments .segment,
-.ui.mini.segment {
- font-size: 0.78571429rem;
-}
-
-.ui.tiny.segments .segment,
-.ui.tiny.segment {
- font-size: 0.85714286rem;
-}
-
-.ui.small.segments .segment,
-.ui.small.segment {
- font-size: 0.92857143rem;
-}
-
-.ui.large.segments .segment,
-.ui.large.segment {
- font-size: 1.14285714rem;
-}
-
-.ui.big.segments .segment,
-.ui.big.segment {
- font-size: 1.28571429rem;
-}
-
-.ui.huge.segments .segment,
-.ui.huge.segment {
- font-size: 1.42857143rem;
-}
-
-.ui.massive.segments .segment,
-.ui.massive.segment {
- font-size: 1.71428571rem;
-}
-
-/*******************************
- Theme Overrides
-*******************************/
-
-/*******************************
- Site Overrides
-*******************************/
-/*!
* # Fomantic-UI - Tab
* http://github.com/fomantic/Fomantic-UI/
*
@@ -15036,1366 +12362,4 @@ Floated Menu / Item
/*******************************
User Overrides
-*******************************/
-/*!
- * # Fomantic-UI - Table
- * http://github.com/fomantic/Fomantic-UI/
- *
- *
- * Released under the MIT license
- * http://opensource.org/licenses/MIT
- *
- */
-
-/*******************************
- Table
-*******************************/
-
-/* Prototype */
-
-.ui.table {
- width: 100%;
- background: #FFFFFF;
- margin: 1em 0;
- border: 1px solid rgba(34, 36, 38, 0.15);
- box-shadow: none;
- border-radius: 0.28571429rem;
- text-align: left;
- vertical-align: middle;
- color: rgba(0, 0, 0, 0.87);
- border-collapse: separate;
- border-spacing: 0;
-}
-
-.ui.table:first-child {
- margin-top: 0;
-}
-
-.ui.table:last-child {
- margin-bottom: 0;
-}
-
-.ui.table > thead,
-.ui.table > tbody {
- text-align: inherit;
- vertical-align: inherit;
-}
-
-/*******************************
- Parts
-*******************************/
-
-/* Table Content */
-
-.ui.table th,
-.ui.table td {
- transition: background 0.1s ease, color 0.1s ease;
-}
-
-/* Rowspan helper class */
-
-.ui.table th.rowspanned,
-.ui.table td.rowspanned {
- display: none;
-}
-
-/* Headers */
-
-.ui.table > thead {
- box-shadow: none;
-}
-
-.ui.table > thead > tr > th {
- cursor: auto;
- background: #F9FAFB;
- text-align: inherit;
- color: rgba(0, 0, 0, 0.87);
- padding: 0.92857143em 0.78571429em;
- vertical-align: inherit;
- font-style: none;
- font-weight: 500;
- text-transform: none;
- border-bottom: 1px solid rgba(34, 36, 38, 0.1);
- border-left: none;
-}
-
-.ui.table > thead > tr > th:first-child {
- border-left: none;
-}
-
-.ui.table > thead > tr:first-child > th:first-child {
- border-radius: 0.28571429rem 0 0 0;
-}
-
-.ui.table > thead > tr:first-child > th:last-child {
- border-radius: 0 0.28571429rem 0 0;
-}
-
-.ui.table > thead > tr:first-child > th:only-child {
- border-radius: 0.28571429rem 0.28571429rem 0 0;
-}
-
-/* Footer */
-
-.ui.table > tfoot {
- box-shadow: none;
-}
-
-.ui.table > tfoot > tr > th,
-.ui.table > tfoot > tr > td {
- cursor: auto;
- border-top: 1px solid rgba(34, 36, 38, 0.15);
- background: #F9FAFB;
- text-align: inherit;
- color: rgba(0, 0, 0, 0.87);
- padding: 0.78571429em 0.78571429em;
- vertical-align: inherit;
- font-style: normal;
- font-weight: normal;
- text-transform: none;
-}
-
-.ui.table > tfoot > tr > th:first-child,
-.ui.table > tfoot > tr > td:first-child {
- border-left: none;
-}
-
-.ui.table > tfoot > tr:first-child > th:first-child,
-.ui.table > tfoot > tr:first-child > td:first-child {
- border-radius: 0 0 0 0.28571429rem;
-}
-
-.ui.table > tfoot > tr:first-child > th:last-child,
-.ui.table > tfoot > tr:first-child > td:last-child {
- border-radius: 0 0 0.28571429rem 0;
-}
-
-.ui.table > tfoot > tr:first-child > th:only-child,
-.ui.table > tfoot > tr:first-child > td:only-child {
- border-radius: 0 0 0.28571429rem 0.28571429rem;
-}
-
-/* Table Row */
-
-.ui.table > tr > td,
-.ui.table > tbody > tr > td {
- border-top: 1px solid rgba(34, 36, 38, 0.1);
-}
-
-.ui.table > tr:first-child > td,
-.ui.table > tbody > tr:first-child > td {
- border-top: none;
-}
-
-/* Repeated tbody */
-
-.ui.table > tbody + tbody tr:first-child > td {
- border-top: 1px solid rgba(34, 36, 38, 0.1);
-}
-
-/* Table Cells */
-
-.ui.table > tbody > tr > td,
-.ui.table > tr > td {
- padding: 0.78571429em 0.78571429em;
- text-align: inherit;
-}
-
-/* Icons */
-
-.ui.table > i.icon {
- vertical-align: baseline;
-}
-
-.ui.table > i.icon:only-child {
- margin: 0;
-}
-
-/* Table Segment */
-
-.ui.table.segment {
- padding: 0;
-}
-
-.ui.table.segment:after {
- display: none;
-}
-
-.ui.table.segment.stacked:after {
- display: block;
-}
-
-/* Responsive */
-
-@media only screen and (max-width: 767.98px) {
- .ui.table:not(.unstackable) {
- width: 100%;
- padding: 0;
- }
-
- .ui.table:not(.unstackable) > thead,
- .ui.table:not(.unstackable) > thead > tr,
- .ui.table:not(.unstackable) > tfoot,
- .ui.table:not(.unstackable) > tfoot > tr,
- .ui.table:not(.unstackable) > tbody,
- .ui.table:not(.unstackable) > tr,
- .ui.table:not(.unstackable) > tbody > tr,
- .ui.table:not(.unstackable) > tr > th:not(.rowspanned),
- .ui.table:not(.unstackable) > thead > tr > th:not(.rowspanned),
- .ui.table:not(.unstackable) > tbody > tr > th:not(.rowspanned),
- .ui.table:not(.unstackable) > tfoot > tr > th:not(.rowspanned),
- .ui.table:not(.unstackable) > tr > td:not(.rowspanned),
- .ui.table:not(.unstackable) > tbody > tr > td:not(.rowspanned),
- .ui.table:not(.unstackable) > tfoot > tr > td:not(.rowspanned) {
- display: block !important;
- width: auto !important;
- }
-
- .ui.table:not(.unstackable) > thead {
- display: block;
- }
-
- .ui.table:not(.unstackable) > tfoot {
- display: block;
- }
-
- .ui.ui.ui.ui.table:not(.unstackable) > tr,
- .ui.ui.ui.ui.table:not(.unstackable) > thead > tr,
- .ui.ui.ui.ui.table:not(.unstackable) > tbody > tr,
- .ui.ui.ui.ui.table:not(.unstackable) > tfoot > tr {
- padding-top: 1em;
- padding-bottom: 1em;
- box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.1) inset;
- }
-
- .ui.ui.ui.ui.table:not(.unstackable) > tr > th,
- .ui.ui.ui.ui.table:not(.unstackable) > thead > tr > th,
- .ui.ui.ui.ui.table:not(.unstackable) > tbody > tr > th,
- .ui.ui.ui.ui.table:not(.unstackable) > tfoot > tr > th,
- .ui.ui.ui.ui.table:not(.unstackable) > tr > td,
- .ui.ui.ui.ui.table:not(.unstackable) > tbody > tr > td,
- .ui.ui.ui.ui.table:not(.unstackable) > tfoot > tr > td {
- background: none;
- border: none;
- padding: 0.25em 0.75em;
- box-shadow: none;
- }
-
- .ui.table:not(.unstackable) > tr > th:first-child,
- .ui.table:not(.unstackable) > thead > tr > th:first-child,
- .ui.table:not(.unstackable) > tbody > tr > th:first-child,
- .ui.table:not(.unstackable) > tfoot > tr > th:first-child,
- .ui.table:not(.unstackable) > tr > td:first-child,
- .ui.table:not(.unstackable) > tbody > tr > td:first-child,
- .ui.table:not(.unstackable) > tfoot > tr > td:first-child {
- font-weight: 500;
- }
-
- /* Definition Table */
-
- .ui.definition.table:not(.unstackable) > thead > tr > th:first-child {
- box-shadow: none !important;
- }
-}
-
-/*******************************
- Coupling
-*******************************/
-
-/* UI Image */
-
-.ui.table .collapsing .image,
-.ui.table .collapsing .image img {
- max-width: none;
-}
-
-/*******************************
- Types
-*******************************/
-
-/*--------------
- Complex
----------------*/
-
-.ui.structured.table {
- border-collapse: collapse;
-}
-
-.ui.structured.table > thead > tr > th {
- border-left: none;
- border-right: none;
-}
-
-.ui.structured.sortable.table > thead > tr > th {
- border-left: 1px solid rgba(34, 36, 38, 0.15);
- border-right: 1px solid rgba(34, 36, 38, 0.15);
-}
-
-.ui.structured.basic.table > tr > th,
-.ui.structured.basic.table > thead > tr > th,
-.ui.structured.basic.table > tbody > tr > th,
-.ui.structured.basic.table > tfoot > tr > th {
- border-left: none;
- border-right: none;
-}
-
-.ui.structured.celled.table > tr > th,
-.ui.structured.celled.table > thead > tr > th,
-.ui.structured.celled.table > tbody > tr > th,
-.ui.structured.celled.table > tfoot > tr > th,
-.ui.structured.celled.table > tr > td,
-.ui.structured.celled.table > tbody > tr > td,
-.ui.structured.celled.table > tfoot > tr > td {
- border-left: 1px solid rgba(34, 36, 38, 0.1);
- border-right: 1px solid rgba(34, 36, 38, 0.1);
-}
-
-/*--------------
- Definition
- ---------------*/
-
-.ui.definition.table > thead:not(.full-width) > tr > th:first-child {
- pointer-events: none;
- background: #FFFFFF;
- font-weight: normal;
- color: rgba(0, 0, 0, 0.4);
- box-shadow: -0.1em -0.2em 0 0.1em #FFFFFF;
- -moz-transform: scale(1);
-}
-
-.ui.definition.table > tfoot:not(.full-width) > tr > th:first-child {
- pointer-events: none;
- background: #FFFFFF;
- font-weight: normal;
- color: rgba(0, 0, 0, 0.4);
- box-shadow: -0.1em 0.2em 0 0.1em #FFFFFF;
- -moz-transform: scale(1);
-}
-
-/* Highlight Defining Column */
-
-.ui.definition.table > tr > td:first-child:not(.ignored),
-.ui.definition.table > tbody > tr > td:first-child:not(.ignored),
-.ui.definition.table > tfoot > tr > td:first-child:not(.ignored),
-.ui.definition.table tr td.definition {
- background: rgba(0, 0, 0, 0.03);
- font-weight: 500;
- color: rgba(0, 0, 0, 0.95);
- text-transform: '';
- box-shadow: '';
- text-align: '';
- font-size: 1em;
- padding-left: '';
- padding-right: '';
-}
-
-/* Fix 2nd Column */
-
-.ui.definition.table > thead:not(.full-width) > tr > th:nth-child(2) {
- border-left: 1px solid rgba(34, 36, 38, 0.15);
-}
-
-.ui.definition.table > tfoot:not(.full-width) > tr > th:nth-child(2),
-.ui.definition.table > tfoot:not(.full-width) > tr > td:nth-child(2) {
- border-left: 1px solid rgba(34, 36, 38, 0.15);
-}
-
-.ui.definition.table > tr > td:nth-child(2),
-.ui.definition.table > tbody > tr > td:nth-child(2) {
- border-left: 1px solid rgba(34, 36, 38, 0.15);
-}
-
-/*******************************
- States
-*******************************/
-
-/*--------------
- Positive
- ---------------*/
-
-.ui.ui.ui.ui.table tr.positive,
-.ui.ui.table td.positive {
- box-shadow: 0 0 0 #A3C293 inset;
- background: #FCFFF5;
- color: #2C662D;
-}
-
-/*--------------
- Negative
- ---------------*/
-
-.ui.ui.ui.ui.table tr.negative,
-.ui.ui.table td.negative {
- box-shadow: 0 0 0 #E0B4B4 inset;
- background: #FFF6F6;
- color: #9F3A38;
-}
-
-/*--------------
- Error
- ---------------*/
-
-.ui.ui.ui.ui.table tr.error,
-.ui.ui.table td.error {
- box-shadow: 0 0 0 #E0B4B4 inset;
- background: #FFF6F6;
- color: #9F3A38;
-}
-
-/*--------------
- Warning
- ---------------*/
-
-.ui.ui.ui.ui.table tr.warning,
-.ui.ui.table td.warning {
- box-shadow: 0 0 0 #C9BA9B inset;
- background: #FFFAF3;
- color: #573A08;
-}
-
-/*--------------
- Active
- ---------------*/
-
-.ui.ui.ui.ui.table tr.active,
-.ui.ui.table td.active {
- box-shadow: 0 0 0 rgba(0, 0, 0, 0.87) inset;
- background: #E0E0E0;
- color: rgba(0, 0, 0, 0.87);
-}
-
-/*--------------
- Disabled
- ---------------*/
-
-.ui.table tr.disabled td,
-.ui.table tr td.disabled,
-.ui.table tr.disabled:hover,
-.ui.table tr:hover td.disabled {
- pointer-events: none;
- color: rgba(40, 40, 40, 0.3);
-}
-
-/*******************************
- Variations
-*******************************/
-
-/*--------------
- Text Alignment
- ---------------*/
-
-.ui.table[class*="left aligned"],
-.ui.table [class*="left aligned"] {
- text-align: left;
-}
-
-.ui.table[class*="center aligned"],
-.ui.table [class*="center aligned"] {
- text-align: center;
-}
-
-.ui.table[class*="right aligned"],
-.ui.table [class*="right aligned"] {
- text-align: right;
-}
-
-/*------------------
- Vertical Alignment
- ------------------*/
-
-.ui.table[class*="top aligned"],
-.ui.table [class*="top aligned"] {
- vertical-align: top;
-}
-
-.ui.table[class*="middle aligned"],
-.ui.table [class*="middle aligned"] {
- vertical-align: middle;
-}
-
-.ui.table[class*="bottom aligned"],
-.ui.table [class*="bottom aligned"] {
- vertical-align: bottom;
-}
-
-/*--------------
- Collapsing
- ---------------*/
-
-.ui.table th.collapsing,
-.ui.table td.collapsing {
- width: 1px;
- white-space: nowrap;
-}
-
-/*--------------
- Fixed
- ---------------*/
-
-.ui.fixed.table {
- table-layout: fixed;
-}
-
-.ui.fixed.table th,
-.ui.fixed.table td {
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-/*--------------
- Selectable
- ---------------*/
-
-.ui.ui.selectable.table > tbody > tr:hover,
-.ui.table tbody tr td.selectable:hover {
- background: rgba(0, 0, 0, 0.05);
- color: rgba(0, 0, 0, 0.95);
-}
-
-/* Selectable Cell Link */
-
-.ui.table tbody tr td.selectable {
- padding: 0;
-}
-
-.ui.table tbody tr td.selectable > a:not(.ui) {
- display: block;
- color: inherit;
- padding: 0.78571429em 0.78571429em;
-}
-
-.ui.table > tr > td.selectable,
-.ui.table > tbody > tr > td.selectable,
-.ui.selectable.table > tbody > tr,
-.ui.selectable.table > tr {
- cursor: pointer;
-}
-
-/* Other States */
-
-.ui.ui.selectable.table tr.error:hover,
-.ui.table tr td.selectable.error:hover,
-.ui.selectable.table tr:hover td.error {
- background: #ffe7e7;
- color: #943634;
-}
-
-.ui.ui.selectable.table tr.warning:hover,
-.ui.table tr td.selectable.warning:hover,
-.ui.selectable.table tr:hover td.warning {
- background: #fff4e4;
- color: #493107;
-}
-
-.ui.ui.selectable.table tr.active:hover,
-.ui.table tr td.selectable.active:hover,
-.ui.selectable.table tr:hover td.active {
- background: #E0E0E0;
- color: rgba(0, 0, 0, 0.87);
-}
-
-.ui.ui.selectable.table tr.positive:hover,
-.ui.table tr td.selectable.positive:hover,
-.ui.selectable.table tr:hover td.positive {
- background: #f7ffe6;
- color: #275b28;
-}
-
-.ui.ui.selectable.table tr.negative:hover,
-.ui.table tr td.selectable.negative:hover,
-.ui.selectable.table tr:hover td.negative {
- background: #ffe7e7;
- color: #943634;
-}
-
-/*-------------------
- Attached
- --------------------*/
-
-/* Middle */
-
-.ui.attached.table {
- top: 0;
- bottom: 0;
- border-radius: 0;
- margin: 0 -1px;
- width: calc(100% + 2px);
- max-width: calc(100% + 2px);
- box-shadow: none;
- border: 1px solid #D4D4D5;
-}
-
-.ui.attached + .ui.attached.table:not(.top) {
- border-top: none;
-}
-
-/* Top */
-
-.ui[class*="top attached"].table {
- bottom: 0;
- margin-bottom: 0;
- top: 0;
- margin-top: 1em;
- border-radius: 0.28571429rem 0.28571429rem 0 0;
-}
-
-.ui.table[class*="top attached"]:first-child {
- margin-top: 0;
-}
-
-/* Bottom */
-
-.ui[class*="bottom attached"].table {
- bottom: 0;
- margin-top: 0;
- top: 0;
- margin-bottom: 1em;
- box-shadow: none, none;
- border-radius: 0 0 0.28571429rem 0.28571429rem;
-}
-
-.ui[class*="bottom attached"].table:last-child {
- margin-bottom: 0;
-}
-
-/*--------------
- Striped
- ---------------*/
-
-/* Table Striping */
-
-.ui.striped.table > tr:nth-child(2n),
-.ui.striped.table > tbody > tr:nth-child(2n) {
- background-color: rgba(0, 0, 50, 0.02);
-}
-
-/* Allow striped active hover */
-
-.ui.striped.selectable.selectable.selectable.table tbody tr.active:hover {
- background: #EFEFEF;
- color: rgba(0, 0, 0, 0.95);
-}
-
-/*--------------
- Single Line
----------------*/
-
-.ui.table[class*="single line"],
-.ui.table [class*="single line"] {
- white-space: nowrap;
-}
-
-/*-------------------
- Colors
---------------------*/
-
-.ui.primary.table {
- border-top: 0.2em solid #2185D0;
-}
-
-.ui.ui.ui.ui.table tr.primary:not(.marked),
-.ui.ui.table td.primary:not(.marked) {
- background: #ddf4ff;
- color: rgba(255, 255, 255, 0.9);
-}
-
-.ui.ui.selectable.table tr.primary:not(.marked):hover,
-.ui.table tr td.selectable.primary:not(.marked):hover,
-.ui.selectable.table tr:hover td.primary:not(.marked) {
- background: #d3f1ff;
- color: rgba(255, 255, 255, 0.9);
-}
-
-.ui.secondary.table {
- border-top: 0.2em solid #1B1C1D;
-}
-
-.ui.ui.ui.ui.table tr.secondary:not(.marked),
-.ui.ui.table td.secondary:not(.marked) {
- background: #dddddd;
- color: rgba(255, 255, 255, 0.9);
-}
-
-.ui.ui.selectable.table tr.secondary:not(.marked):hover,
-.ui.table tr td.selectable.secondary:not(.marked):hover,
-.ui.selectable.table tr:hover td.secondary:not(.marked) {
- background: #e2e2e2;
- color: rgba(255, 255, 255, 0.9);
-}
-
-.ui.red.table {
- border-top: 0.2em solid #DB2828;
-}
-
-.ui.ui.ui.ui.table tr.red:not(.marked),
-.ui.ui.table td.red:not(.marked) {
- background: #ffe1df;
- color: #DB2828;
-}
-
-.ui.ui.selectable.table tr.red:not(.marked):hover,
-.ui.table tr td.selectable.red:not(.marked):hover,
-.ui.selectable.table tr:hover td.red:not(.marked) {
- background: #ffd7d5;
- color: #DB2828;
-}
-
-.ui.orange.table {
- border-top: 0.2em solid #F2711C;
-}
-
-.ui.ui.ui.ui.table tr.orange:not(.marked),
-.ui.ui.table td.orange:not(.marked) {
- background: #ffe7d1;
- color: #F2711C;
-}
-
-.ui.ui.selectable.table tr.orange:not(.marked):hover,
-.ui.table tr td.selectable.orange:not(.marked):hover,
-.ui.selectable.table tr:hover td.orange:not(.marked) {
- background: #fae1cc;
- color: #F2711C;
-}
-
-.ui.yellow.table {
- border-top: 0.2em solid #FBBD08;
-}
-
-.ui.ui.ui.ui.table tr.yellow:not(.marked),
-.ui.ui.table td.yellow:not(.marked) {
- background: #fff9d2;
- color: #B58105;
-}
-
-.ui.ui.selectable.table tr.yellow:not(.marked):hover,
-.ui.table tr td.selectable.yellow:not(.marked):hover,
-.ui.selectable.table tr:hover td.yellow:not(.marked) {
- background: #fbf5cc;
- color: #B58105;
-}
-
-.ui.olive.table {
- border-top: 0.2em solid #B5CC18;
-}
-
-.ui.ui.ui.ui.table tr.olive:not(.marked),
-.ui.ui.table td.olive:not(.marked) {
- background: #f7fae4;
- color: #8ABC1E;
-}
-
-.ui.ui.selectable.table tr.olive:not(.marked):hover,
-.ui.table tr td.selectable.olive:not(.marked):hover,
-.ui.selectable.table tr:hover td.olive:not(.marked) {
- background: #f6fada;
- color: #8ABC1E;
-}
-
-.ui.green.table {
- border-top: 0.2em solid #21BA45;
-}
-
-.ui.ui.ui.ui.table tr.green:not(.marked),
-.ui.ui.table td.green:not(.marked) {
- background: #d5f5d9;
- color: #1EBC30;
-}
-
-.ui.ui.selectable.table tr.green:not(.marked):hover,
-.ui.table tr td.selectable.green:not(.marked):hover,
-.ui.selectable.table tr:hover td.green:not(.marked) {
- background: #d2eed5;
- color: #1EBC30;
-}
-
-.ui.teal.table {
- border-top: 0.2em solid #00B5AD;
-}
-
-.ui.ui.ui.ui.table tr.teal:not(.marked),
-.ui.ui.table td.teal:not(.marked) {
- background: #e2ffff;
- color: #10A3A3;
-}
-
-.ui.ui.selectable.table tr.teal:not(.marked):hover,
-.ui.table tr td.selectable.teal:not(.marked):hover,
-.ui.selectable.table tr:hover td.teal:not(.marked) {
- background: #d8ffff;
- color: #10A3A3;
-}
-
-.ui.blue.table {
- border-top: 0.2em solid #2185D0;
-}
-
-.ui.ui.ui.ui.table tr.blue:not(.marked),
-.ui.ui.table td.blue:not(.marked) {
- background: #ddf4ff;
- color: #2185D0;
-}
-
-.ui.ui.selectable.table tr.blue:not(.marked):hover,
-.ui.table tr td.selectable.blue:not(.marked):hover,
-.ui.selectable.table tr:hover td.blue:not(.marked) {
- background: #d3f1ff;
- color: #2185D0;
-}
-
-.ui.violet.table {
- border-top: 0.2em solid #6435C9;
-}
-
-.ui.ui.ui.ui.table tr.violet:not(.marked),
-.ui.ui.table td.violet:not(.marked) {
- background: #ece9fe;
- color: #6435C9;
-}
-
-.ui.ui.selectable.table tr.violet:not(.marked):hover,
-.ui.table tr td.selectable.violet:not(.marked):hover,
-.ui.selectable.table tr:hover td.violet:not(.marked) {
- background: #e3deff;
- color: #6435C9;
-}
-
-.ui.purple.table {
- border-top: 0.2em solid #A333C8;
-}
-
-.ui.ui.ui.ui.table tr.purple:not(.marked),
-.ui.ui.table td.purple:not(.marked) {
- background: #f8e3ff;
- color: #A333C8;
-}
-
-.ui.ui.selectable.table tr.purple:not(.marked):hover,
-.ui.table tr td.selectable.purple:not(.marked):hover,
-.ui.selectable.table tr:hover td.purple:not(.marked) {
- background: #f5d9ff;
- color: #A333C8;
-}
-
-.ui.pink.table {
- border-top: 0.2em solid #E03997;
-}
-
-.ui.ui.ui.ui.table tr.pink:not(.marked),
-.ui.ui.table td.pink:not(.marked) {
- background: #ffe8f9;
- color: #E03997;
-}
-
-.ui.ui.selectable.table tr.pink:not(.marked):hover,
-.ui.table tr td.selectable.pink:not(.marked):hover,
-.ui.selectable.table tr:hover td.pink:not(.marked) {
- background: #ffdef6;
- color: #E03997;
-}
-
-.ui.brown.table {
- border-top: 0.2em solid #A5673F;
-}
-
-.ui.ui.ui.ui.table tr.brown:not(.marked),
-.ui.ui.table td.brown:not(.marked) {
- background: #f7e5d2;
- color: #A5673F;
-}
-
-.ui.ui.selectable.table tr.brown:not(.marked):hover,
-.ui.table tr td.selectable.brown:not(.marked):hover,
-.ui.selectable.table tr:hover td.brown:not(.marked) {
- background: #efe0cf;
- color: #A5673F;
-}
-
-.ui.grey.table {
- border-top: 0.2em solid #767676;
-}
-
-.ui.ui.ui.ui.table tr.grey:not(.marked),
-.ui.ui.table td.grey:not(.marked) {
- background: #DCDDDE;
- color: #767676;
-}
-
-.ui.ui.selectable.table tr.grey:not(.marked):hover,
-.ui.table tr td.selectable.grey:not(.marked):hover,
-.ui.selectable.table tr:hover td.grey:not(.marked) {
- background: #c2c4c5;
- color: #767676;
-}
-
-.ui.black.table {
- border-top: 0.2em solid #1B1C1D;
-}
-
-.ui.ui.ui.ui.table tr.black:not(.marked),
-.ui.ui.table td.black:not(.marked) {
- background: #545454;
- color: #FFFFFF;
-}
-
-.ui.ui.selectable.table tr.black:not(.marked):hover,
-.ui.table tr td.selectable.black:not(.marked):hover,
-.ui.selectable.table tr:hover td.black:not(.marked) {
- background: #000000;
- color: #FFFFFF;
-}
-
-/*--------------
- Column Count
----------------*/
-
-/* Grid Based */
-
-.ui.one.column.table td {
- width: 100%;
-}
-
-.ui.two.column.table td {
- width: 50%;
-}
-
-.ui.three.column.table td {
- width: 33.33333333%;
-}
-
-.ui.four.column.table td {
- width: 25%;
-}
-
-.ui.five.column.table td {
- width: 20%;
-}
-
-.ui.six.column.table td {
- width: 16.66666667%;
-}
-
-.ui.seven.column.table td {
- width: 14.28571429%;
-}
-
-.ui.eight.column.table td {
- width: 12.5%;
-}
-
-.ui.nine.column.table td {
- width: 11.11111111%;
-}
-
-.ui.ten.column.table td {
- width: 10%;
-}
-
-.ui.eleven.column.table td {
- width: 9.09090909%;
-}
-
-.ui.twelve.column.table td {
- width: 8.33333333%;
-}
-
-.ui.thirteen.column.table td {
- width: 7.69230769%;
-}
-
-.ui.fourteen.column.table td {
- width: 7.14285714%;
-}
-
-.ui.fifteen.column.table td {
- width: 6.66666667%;
-}
-
-.ui.sixteen.column.table td {
- width: 6.25%;
-}
-
-/* Column Width */
-
-.ui.table th.one.wide,
-.ui.table td.one.wide {
- width: 6.25%;
-}
-
-.ui.table th.two.wide,
-.ui.table td.two.wide {
- width: 12.5%;
-}
-
-.ui.table th.three.wide,
-.ui.table td.three.wide {
- width: 18.75%;
-}
-
-.ui.table th.four.wide,
-.ui.table td.four.wide {
- width: 25%;
-}
-
-.ui.table th.five.wide,
-.ui.table td.five.wide {
- width: 31.25%;
-}
-
-.ui.table th.six.wide,
-.ui.table td.six.wide {
- width: 37.5%;
-}
-
-.ui.table th.seven.wide,
-.ui.table td.seven.wide {
- width: 43.75%;
-}
-
-.ui.table th.eight.wide,
-.ui.table td.eight.wide {
- width: 50%;
-}
-
-.ui.table th.nine.wide,
-.ui.table td.nine.wide {
- width: 56.25%;
-}
-
-.ui.table th.ten.wide,
-.ui.table td.ten.wide {
- width: 62.5%;
-}
-
-.ui.table th.eleven.wide,
-.ui.table td.eleven.wide {
- width: 68.75%;
-}
-
-.ui.table th.twelve.wide,
-.ui.table td.twelve.wide {
- width: 75%;
-}
-
-.ui.table th.thirteen.wide,
-.ui.table td.thirteen.wide {
- width: 81.25%;
-}
-
-.ui.table th.fourteen.wide,
-.ui.table td.fourteen.wide {
- width: 87.5%;
-}
-
-.ui.table th.fifteen.wide,
-.ui.table td.fifteen.wide {
- width: 93.75%;
-}
-
-.ui.table th.sixteen.wide,
-.ui.table td.sixteen.wide {
- width: 100%;
-}
-
-/*--------------
- Sortable
- ---------------*/
-
-.ui.sortable.table > thead > tr > th {
- cursor: pointer;
- white-space: nowrap;
- border-left: 1px solid rgba(34, 36, 38, 0.15);
- color: rgba(0, 0, 0, 0.87);
-}
-
-.ui.sortable.table > thead > tr > th:first-child {
- border-left: none;
-}
-
-.ui.sortable.table thead th.sorted,
-.ui.sortable.table thead th.sorted:hover {
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
-}
-
-.ui.sortable.table > thead > tr > th:after {
- display: none;
- font-style: normal;
- font-weight: normal;
- text-decoration: inherit;
- content: '';
- height: 1em;
- width: auto;
- opacity: 0.8;
- margin: 0 0 0 0.5em;
- font-family: 'Icons';
-}
-
-.ui.sortable.table thead th.ascending:after {
- content: '\f0d8';
-}
-
-.ui.sortable.table thead th.descending:after {
- content: '\f0d7';
-}
-
-/* Hover */
-
-.ui.sortable.table th.disabled:hover {
- cursor: auto;
- color: rgba(40, 40, 40, 0.3);
-}
-
-.ui.sortable.table > thead > tr > th:hover {
- color: rgba(0, 0, 0, 0.8);
-}
-
-.ui.sortable.table:not(.basic) > thead > tr > th:hover {
- background: rgba(0, 0, 0, 0.05);
-}
-
-/* Sorted */
-
-.ui.sortable.table thead th.sorted {
- color: rgba(0, 0, 0, 0.95);
-}
-
-.ui.sortable.table:not(.basic) thead th.sorted {
- background: rgba(0, 0, 0, 0.05);
-}
-
-.ui.sortable.table thead th.sorted:after {
- display: inline-block;
-}
-
-/* Sorted Hover */
-
-.ui.sortable.table thead th.sorted:hover {
- color: rgba(0, 0, 0, 0.95);
-}
-
-.ui.sortable.table:not(.basic) thead th.sorted:hover {
- background: rgba(0, 0, 0, 0.05);
-}
-
-/*--------------
- Collapsing
- ---------------*/
-
-.ui.collapsing.table {
- width: auto;
-}
-
-/*--------------
- Basic
- ---------------*/
-
-.ui.basic.table {
- background: transparent;
- border: 1px solid rgba(34, 36, 38, 0.15);
- box-shadow: none;
-}
-
-.ui.basic.table > thead,
-.ui.basic.table > tfoot {
- box-shadow: none;
-}
-
-.ui.basic.table > thead > tr > th,
-.ui.basic.table > tbody > tr > th,
-.ui.basic.table > tfoot > tr > th,
-.ui.basic.table > tr > th {
- background: transparent;
- border-left: none;
-}
-
-.ui.basic.table > tbody > tr {
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
-}
-
-.ui.basic.table > tbody > tr > td,
-.ui.basic.table > tfoot > tr > td,
-.ui.basic.table > tr > td {
- background: transparent;
-}
-
-.ui.basic.striped.table > tbody > tr:nth-child(2n) {
- background-color: rgba(0, 0, 0, 0.05);
-}
-
-/* Very Basic */
-
-.ui[class*="very basic"].table {
- border: none;
-}
-
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tr > th,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > thead > tr > th,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tbody > tr > th,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tfoot > tr > th,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tr > td,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tbody > tr > td {
- padding: '';
-}
-
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tr > th:first-child,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > thead > tr > th:first-child,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tbody > tr > th:first-child,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tfoot > tr > th:first-child,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tr > td:first-child,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tbody > tr > td:first-child,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tfoot > tr > td:first-child {
- padding-left: 0;
-}
-
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tr > th:last-child,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > thead > tr > th:last-child,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tbody > tr > th:last-child,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tfoot > tr > th:last-child,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tr > td:last-child,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tbody > tr > td:last-child,
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > tfoot > tr > td:last-child {
- padding-right: 0;
-}
-
-.ui[class*="very basic"].table:not(.sortable):not(.striped) > thead > tr:first-child > th {
- padding-top: 0;
-}
-
-/*--------------
- Celled
- ---------------*/
-
-.ui.celled.table > tr > th,
-.ui.celled.table > thead > tr > th,
-.ui.celled.table > tbody > tr > th,
-.ui.celled.table > tfoot > tr > th,
-.ui.celled.table > tr > td,
-.ui.celled.table > tbody > tr > td,
-.ui.celled.table > tfoot > tr > td {
- border-left: 1px solid rgba(34, 36, 38, 0.1);
-}
-
-.ui.celled.table > tr > th:first-child,
-.ui.celled.table > thead > tr > th:first-child,
-.ui.celled.table > tbody > tr > th:first-child,
-.ui.celled.table > tfoot > tr > th:first-child,
-.ui.celled.table > tr > td:first-child,
-.ui.celled.table > tbody > tr > td:first-child,
-.ui.celled.table > tfoot > tr > td:first-child {
- border-left: none;
-}
-
-/*--------------
- Padded
- ---------------*/
-
-.ui.padded.table > tr > th,
-.ui.padded.table > thead > tr > th,
-.ui.padded.table > tbody > tr > th,
-.ui.padded.table > tfoot > tr > th {
- padding-left: 1em;
- padding-right: 1em;
-}
-
-.ui.padded.table > tr > th,
-.ui.padded.table > thead > tr > th,
-.ui.padded.table > tbody > tr > th,
-.ui.padded.table > tfoot > tr > th,
-.ui.padded.table > tr > td,
-.ui.padded.table > tbody > tr > td,
-.ui.padded.table > tfoot > tr > td {
- padding: 1em 1em;
-}
-
-/* Very */
-
-.ui[class*="very padded"].table > tr > th,
-.ui[class*="very padded"].table > thead > tr > th,
-.ui[class*="very padded"].table > tbody > tr > th,
-.ui[class*="very padded"].table > tfoot > tr > th {
- padding-left: 1.5em;
- padding-right: 1.5em;
-}
-
-.ui[class*="very padded"].table > tr > td,
-.ui[class*="very padded"].table > tbody > tr > td,
-.ui[class*="very padded"].table > tfoot > tr > td {
- padding: 1.5em 1.5em;
-}
-
-/*--------------
- Compact
- ---------------*/
-
-.ui.compact.table > tr > th,
-.ui.compact.table > thead > tr > th,
-.ui.compact.table > tbody > tr > th,
-.ui.compact.table > tfoot > tr > th {
- padding-left: 0.7em;
- padding-right: 0.7em;
-}
-
-.ui.compact.table > tr > td,
-.ui.compact.table > tbody > tr > td,
-.ui.compact.table > tfoot > tr > td {
- padding: 0.5em 0.7em;
-}
-
-/* Very */
-
-.ui[class*="very compact"].table > tr > th,
-.ui[class*="very compact"].table > thead > tr > th,
-.ui[class*="very compact"].table > tbody > tr > th,
-.ui[class*="very compact"].table > tfoot > tr > th {
- padding-left: 0.6em;
- padding-right: 0.6em;
-}
-
-.ui[class*="very compact"].table > tr > td,
-.ui[class*="very compact"].table > tbody > tr > td,
-.ui[class*="very compact"].table > tfoot > tr > td {
- padding: 0.4em 0.6em;
-}
-
-/*--------------
- Sizes
----------------*/
-
-/* Standard */
-
-.ui.table {
- font-size: 1em;
-}
-
-.ui.mini.table {
- font-size: 0.78571429rem;
-}
-
-.ui.tiny.table {
- font-size: 0.85714286rem;
-}
-
-.ui.small.table {
- font-size: 0.9em;
-}
-
-.ui.large.table {
- font-size: 1.1em;
-}
-
-.ui.big.table {
- font-size: 1.28571429rem;
-}
-
-.ui.huge.table {
- font-size: 1.42857143rem;
-}
-
-.ui.massive.table {
- font-size: 1.71428571rem;
-}
-
-/*******************************
- Site Overrides
*******************************/ \ No newline at end of file
diff --git a/web_src/fomantic/semantic.json b/web_src/fomantic/semantic.json
index 367bdf3642..b916af6922 100644
--- a/web_src/fomantic/semantic.json
+++ b/web_src/fomantic/semantic.json
@@ -24,19 +24,14 @@
"api",
"button",
"checkbox",
- "container",
"dimmer",
"dropdown",
"form",
- "header",
"input",
- "label",
"list",
"menu",
"modal",
"search",
- "segment",
- "tab",
- "table"
+ "tab"
]
}
diff --git a/web_src/js/components/ContextPopup.test.js b/web_src/js/components/ContextPopup.test.js
new file mode 100644
index 0000000000..1db6c38301
--- /dev/null
+++ b/web_src/js/components/ContextPopup.test.js
@@ -0,0 +1,39 @@
+import {mount, flushPromises} from '@vue/test-utils';
+import ContextPopup from './ContextPopup.vue';
+
+test('renders a issue info popup', async () => {
+ const owner = 'user2';
+ const repo = 'repo1';
+ const index = 1;
+ vi.spyOn(global, 'fetch').mockResolvedValue({
+ json: vi.fn().mockResolvedValue({
+ ok: true,
+ created_at: '2023-09-30T19:00:00Z',
+ repository: {full_name: owner},
+ pull_request: null,
+ state: 'open',
+ title: 'Normal issue',
+ body: 'Lorem ipsum...',
+ number: index,
+ labels: [{color: 'ee0701', name: "Bug :+1: <script class='evil'>alert('Oh no!');</script>"}],
+ }),
+ ok: true,
+ });
+
+ const wrapper = mount(ContextPopup);
+ wrapper.vm.$el.dispatchEvent(new CustomEvent('ce-load-context-popup', {detail: {owner, repo, index}}));
+ await flushPromises();
+
+ // Header
+ expect(wrapper.get('p:nth-of-type(1)').text()).toEqual('user2 on Sep 30, 2023');
+ // Title
+ expect(wrapper.get('p:nth-of-type(2)').text()).toEqual('Normal issue #1');
+ // Body
+ expect(wrapper.get('p:nth-of-type(3)').text()).toEqual('Lorem ipsum...');
+ // Check that the state is correct.
+ expect(wrapper.get('svg').classes()).toContain('octicon-issue-opened');
+ // Ensure that script is not an element.
+ expect(() => wrapper.get('.evil')).toThrowError();
+ // Check content of label
+ expect(wrapper.get('.ui.label').text()).toContain("Bug 👠<script class='evil'>alert('Oh no!');</script>");
+});
diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue
index 149cabd41e..ac6a8f3bb6 100644
--- a/web_src/js/components/ContextPopup.vue
+++ b/web_src/js/components/ContextPopup.vue
@@ -3,6 +3,8 @@ import {SvgIcon} from '../svg.js';
import {useLightTextOnBackground} from '../utils/color.js';
import tinycolor from 'tinycolor2';
import {GET} from '../modules/fetch.js';
+import {emojiHTML} from '../features/emoji.js';
+import {htmlEscape} from 'escape-goat';
const {appSubUrl, i18n} = window.config;
@@ -67,6 +69,10 @@ export default {
} else {
textColor = '#111111';
}
+ label.name = htmlEscape(label.name);
+ label.name = label.name.replaceAll(/:[-+\w]+:/g, (emoji) => {
+ return emojiHTML(emoji.substring(1, emoji.length - 1));
+ });
return {name: label.name, color: `#${label.color}`, textColor};
});
},
@@ -103,20 +109,14 @@ export default {
</script>
<template>
<div ref="root">
- <div v-if="loading" class="ui active centered inline loader"/>
- <div v-if="!loading && issue !== null">
+ <div v-if="loading" class="tw-h-12 tw-w-12 is-loading"/>
+ <div v-if="!loading && issue !== null" id="issue-info-popup">
<p><small>{{ issue.repository.full_name }} on {{ createdAt }}</small></p>
<p><svg-icon :name="icon" :class="['text', color]"/> <strong>{{ issue.title }}</strong> #{{ issue.number }}</p>
<p>{{ body }}</p>
<div>
- <div
- v-for="label in labels"
- :key="label.name"
- class="ui label"
- :style="{ color: label.textColor, backgroundColor: label.color }"
- >
- {{ label.name }}
- </div>
+ <!-- eslint-disable-next-line vue/no-v-html -->
+ <div v-for="label in labels" :key="label.name" class="ui label" :style="{ color: label.textColor, backgroundColor: label.color }" v-html="label.name"/>
</div>
</div>
<div v-if="!loading && issue === null">
diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue
index 64de258f96..fe956a593a 100644
--- a/web_src/js/components/DashboardRepoList.vue
+++ b/web_src/js/components/DashboardRepoList.vue
@@ -340,14 +340,14 @@ export default sfc; // activate the IDE's Vue plugin
<template>
<div>
<div v-if="!isOrganization" class="ui secondary stackable menu">
- <a :class="{item: true, active: tab === 'repos'}" @click="changeTab('repos')">{{ textMyRepos }} <span class="ui grey label gt-ml-3">{{ reposTotalCount }}</span></a>
- <a :class="{item: true, active: tab === 'organizations'}" @click="changeTab('organizations')">{{ textMyOrgs }} <span class="ui grey label gt-ml-3">{{ organizationsTotalCount }}</span></a>
+ <a :class="{item: true, active: tab === 'repos'}" @click="changeTab('repos')">{{ textMyRepos }} <span class="ui grey label tw-ml-2">{{ reposTotalCount }}</span></a>
+ <a :class="{item: true, active: tab === 'organizations'}" @click="changeTab('organizations')">{{ textMyOrgs }} <span class="ui grey label tw-ml-2">{{ organizationsTotalCount }}</span></a>
</div>
<div v-show="tab === 'repos'" class="ui tab active list dashboard-repos">
- <h4 v-if="isOrganization" class="ui top attached gt-mt-4 tw-flex tw-items-center">
+ <h4 v-if="isOrganization" class="ui top attached tw-mt-4 tw-flex tw-items-center">
<div class="tw-flex-1 tw-flex tw-items-center">
{{ textMyRepos }}
- <span class="ui grey label gt-ml-3">{{ reposTotalCount }}</span>
+ <span class="ui grey label tw-ml-2">{{ reposTotalCount }}</span>
</div>
</h4>
<div class="ui top attached segment repos-search gt-rounded-top">
@@ -363,7 +363,7 @@ export default sfc; // activate the IDE's Vue plugin
otherwise if the "input" handles click event for intermediate status, it breaks the internal state-->
<input type="checkbox" class="hidden" v-bind.prop="checkboxArchivedFilterProps">
<label>
- <svg-icon name="octicon-archive" :size="16" class-name="gt-mr-2"/>
+ <svg-icon name="octicon-archive" :size="16" class-name="tw-mr-1"/>
{{ textShowArchived }}
</label>
</div>
@@ -372,7 +372,7 @@ export default sfc; // activate the IDE's Vue plugin
<div class="ui checkbox" ref="checkboxPrivateFilter" :title="checkboxPrivateFilterTitle">
<input type="checkbox" class="hidden" v-bind.prop="checkboxPrivateFilterProps">
<label>
- <svg-icon name="octicon-lock" :size="16" class-name="gt-mr-2"/>
+ <svg-icon name="octicon-lock" :size="16" class-name="tw-mr-1"/>
{{ textShowPrivate }}
</label>
</div>
@@ -407,7 +407,7 @@ export default sfc; // activate the IDE's Vue plugin
</div>
<div v-if="repos.length" class="ui attached table segment tw-rounded-b">
<ul class="repo-owner-name-list">
- <li class="tw-flex tw-items-center gt-py-3" v-for="repo, index in repos" :class="{'active': index === activeIndex}" :key="repo.id">
+ <li class="tw-flex tw-items-center tw-py-2" v-for="repo, index in repos" :class="{'active': index === activeIndex}" :key="repo.id">
<a class="repo-list-link muted" :href="repo.link">
<svg-icon :name="repoIcon(repo)" :size="16" class-name="repo-list-icon"/>
<div class="text truncate">{{ repo.full_name }}</div>
@@ -417,37 +417,37 @@ export default sfc; // activate the IDE's Vue plugin
</a>
<a class="tw-flex tw-items-center" v-if="repo.latest_commit_status" :href="repo.latest_commit_status.TargetLink" :data-tooltip-content="repo.locale_latest_commit_status.State">
<!-- the commit status icon logic is taken from templates/repo/commit_status.tmpl -->
- <svg-icon :name="statusIcon(repo.latest_commit_status.State)" :class-name="'gt-ml-3 commit-status icon text ' + statusColor(repo.latest_commit_status.State)" :size="16"/>
+ <svg-icon :name="statusIcon(repo.latest_commit_status.State)" :class-name="'tw-ml-2 commit-status icon text ' + statusColor(repo.latest_commit_status.State)" :size="16"/>
</a>
</li>
</ul>
<div v-if="showMoreReposLink" class="tw-text-center">
- <div class="divider gt-my-0"/>
- <div class="ui borderless pagination menu narrow gt-my-3">
+ <div class="divider tw-my-0"/>
+ <div class="ui borderless pagination menu narrow tw-my-2">
<a
- class="item navigation gt-py-2" :class="{'disabled': page === 1}"
+ class="item navigation tw-py-1" :class="{'disabled': page === 1}"
@click="changePage(1)" :title="textFirstPage"
>
- <svg-icon name="gitea-double-chevron-left" :size="16" class-name="gt-mr-2"/>
+ <svg-icon name="gitea-double-chevron-left" :size="16" class-name="tw-mr-1"/>
</a>
<a
- class="item navigation gt-py-2" :class="{'disabled': page === 1}"
+ class="item navigation tw-py-1" :class="{'disabled': page === 1}"
@click="changePage(page - 1)" :title="textPreviousPage"
>
- <svg-icon name="octicon-chevron-left" :size="16" clsas-name="gt-mr-2"/>
+ <svg-icon name="octicon-chevron-left" :size="16" clsas-name="tw-mr-1"/>
</a>
- <a class="active item gt-py-2">{{ page }}</a>
+ <a class="active item tw-py-1">{{ page }}</a>
<a
class="item navigation" :class="{'disabled': page === finalPage}"
@click="changePage(page + 1)" :title="textNextPage"
>
- <svg-icon name="octicon-chevron-right" :size="16" class-name="gt-ml-2"/>
+ <svg-icon name="octicon-chevron-right" :size="16" class-name="tw-ml-1"/>
</a>
<a
- class="item navigation gt-py-2" :class="{'disabled': page === finalPage}"
+ class="item navigation tw-py-1" :class="{'disabled': page === finalPage}"
@click="changePage(finalPage)" :title="textLastPage"
>
- <svg-icon name="gitea-double-chevron-right" :size="16" class-name="gt-ml-2"/>
+ <svg-icon name="gitea-double-chevron-right" :size="16" class-name="tw-ml-1"/>
</a>
</div>
</div>
@@ -456,7 +456,7 @@ export default sfc; // activate the IDE's Vue plugin
<div v-if="!isOrganization" v-show="tab === 'organizations'" class="ui tab active list dashboard-orgs">
<div v-if="organizations.length" class="ui attached table segment tw-rounded-b">
<ul class="repo-owner-name-list">
- <li class="tw-flex tw-items-center gt-py-3" v-for="org in organizations" :key="org.name">
+ <li class="tw-flex tw-items-center tw-py-2" v-for="org in organizations" :key="org.name">
<a class="repo-list-link muted" :href="subUrl + '/' + encodeURIComponent(org.name)">
<svg-icon name="octicon-organization" :size="16" class-name="repo-list-icon"/>
<div class="text truncate">{{ org.name }}</div>
@@ -466,9 +466,9 @@ export default sfc; // activate the IDE's Vue plugin
</span>
</div>
</a>
- <div class="text light grey tw-flex tw-items-center gt-ml-3">
+ <div class="text light grey tw-flex tw-items-center tw-ml-2">
{{ org.num_repos }}
- <svg-icon name="octicon-repo" :size="16" class-name="gt-ml-2 gt-mt-1"/>
+ <svg-icon name="octicon-repo" :size="16" class-name="tw-ml-1 tw-mt-0.5"/>
</div>
</li>
</ul>
diff --git a/web_src/js/components/DiffCommitSelector.vue b/web_src/js/components/DiffCommitSelector.vue
index 35245f2190..352d085731 100644
--- a/web_src/js/components/DiffCommitSelector.vue
+++ b/web_src/js/components/DiffCommitSelector.vue
@@ -103,7 +103,7 @@ export default {
this.menuVisible = !this.menuVisible;
// load our commits when the menu is not yet visible (it'll be toggled after loading)
// and we got no commits
- if (this.commits.length === 0 && this.menuVisible && !this.isLoading) {
+ if (!this.commits.length && this.menuVisible && !this.isLoading) {
this.isLoading = true;
try {
await this.fetchCommits();
@@ -208,7 +208,7 @@ export default {
<div class="gt-ellipsis">
{{ locale.show_all_commits }}
</div>
- <div class="gt-ellipsis text light-2 gt-mb-0">
+ <div class="gt-ellipsis text light-2 tw-mb-0">
{{ locale.stats_num_commits }}
</div>
</div>
@@ -216,7 +216,7 @@ export default {
<div
v-if="lastReviewCommitSha != null" role="menuitem"
class="vertical item"
- :class="{disabled: commitsSinceLastReview === 0}"
+ :class="{disabled: !commitsSinceLastReview}"
@keydown.enter="changesSinceLastReviewClick()"
@click="changesSinceLastReviewClick()"
>
@@ -252,7 +252,7 @@ export default {
</span>
</div>
</div>
- <div class="gt-mono">
+ <div class="tw-font-mono">
{{ commit.short_sha }}
</div>
</div>
diff --git a/web_src/js/components/DiffFileList.vue b/web_src/js/components/DiffFileList.vue
index 66fe49c50b..916780d913 100644
--- a/web_src/js/components/DiffFileList.vue
+++ b/web_src/js/components/DiffFileList.vue
@@ -36,20 +36,20 @@ export default {
};
</script>
<template>
- <ol class="diff-stats gt-m-0" ref="root" v-if="store.fileListIsVisible">
+ <ol class="diff-stats tw-m-0" ref="root" v-if="store.fileListIsVisible">
<li v-for="file in store.files" :key="file.NameHash">
<div class="tw-font-semibold tw-flex tw-items-center pull-right">
- <span v-if="file.IsBin" class="gt-ml-1 gt-mr-3">{{ store.binaryFileMessage }}</span>
+ <span v-if="file.IsBin" class="tw-ml-0.5 tw-mr-2">{{ store.binaryFileMessage }}</span>
{{ file.IsBin ? '' : file.Addition + file.Deletion }}
- <span v-if="!file.IsBin" class="diff-stats-bar gt-mx-3" :data-tooltip-content="store.statisticsMessage.replace('%d', (file.Addition + file.Deletion)).replace('%d', file.Addition).replace('%d', file.Deletion)">
+ <span v-if="!file.IsBin" class="diff-stats-bar tw-mx-2" :data-tooltip-content="store.statisticsMessage.replace('%d', (file.Addition + file.Deletion)).replace('%d', file.Addition).replace('%d', file.Deletion)">
<div class="diff-stats-add-bar" :style="{ 'width': diffStatsWidth(file.Addition, file.Deletion) }"/>
</span>
</div>
<!-- todo finish all file status, now modify, add, delete and rename -->
<span :class="['status', diffTypeToString(file.Type)]" :data-tooltip-content="diffTypeToString(file.Type)">&nbsp;</span>
- <a class="file gt-mono" :href="'#diff-' + file.NameHash">{{ file.Name }}</a>
+ <a class="file tw-font-mono" :href="'#diff-' + file.NameHash">{{ file.Name }}</a>
</li>
- <li v-if="store.isIncomplete" class="gt-pt-2">
+ <li v-if="store.isIncomplete" class="tw-pt-1">
<span class="file tw-flex tw-items-center tw-justify-between">{{ store.tooManyFilesMessage }}
<a :class="['ui', 'basic', 'tiny', 'button', store.isLoadingNewData ? 'disabled' : '']" @click.stop="loadMoreData">{{ store.showMoreMessage }}</a>
</span>
diff --git a/web_src/js/components/DiffFileTree.vue b/web_src/js/components/DiffFileTree.vue
index 83d57b00d1..cddfee1e04 100644
--- a/web_src/js/components/DiffFileTree.vue
+++ b/web_src/js/components/DiffFileTree.vue
@@ -129,7 +129,7 @@ export default {
<div v-if="store.fileTreeIsVisible" class="diff-file-tree-items">
<!-- only render the tree if we're visible. in many cases this is something that doesn't change very often -->
<DiffFileTreeItem v-for="item in fileTree" :key="item.name" :item="item"/>
- <div v-if="store.isIncomplete" class="gt-pt-2">
+ <div v-if="store.isIncomplete" class="tw-pt-1">
<a :class="['ui', 'basic', 'tiny', 'button', store.isLoadingNewData ? 'disabled' : '']" @click.stop="loadMoreData">{{ store.showMoreMessage }}</a>
</div>
</div>
diff --git a/web_src/js/components/PullRequestMergeForm.vue b/web_src/js/components/PullRequestMergeForm.vue
index 35acbdf74f..bd0901a7b5 100644
--- a/web_src/js/components/PullRequestMergeForm.vue
+++ b/web_src/js/components/PullRequestMergeForm.vue
@@ -108,7 +108,7 @@ export default {
<div class="field">
<textarea name="merge_message_field" rows="5" :placeholder="mergeForm.mergeMessageFieldPlaceHolder" v-model="mergeMessageFieldValue"/>
<template v-if="mergeMessageFieldValue !== mergeForm.defaultMergeMessage">
- <button @click.prevent="clearMergeMessage" class="btn gt-mt-2 gt-p-2 interact-fg" :data-tooltip-content="mergeForm.textClearMergeMessageHint">
+ <button @click.prevent="clearMergeMessage" class="btn tw-mt-1 tw-p-1 interact-fg" :data-tooltip-content="mergeForm.textClearMergeMessageHint">
{{ mergeForm.textClearMergeMessage }}
</button>
</template>
@@ -130,7 +130,7 @@ export default {
{{ mergeForm.textCancel }}
</button>
- <div class="ui checkbox gt-ml-2" v-if="mergeForm.isPullBranchDeletable && !autoMergeWhenSucceed">
+ <div class="ui checkbox tw-ml-1" v-if="mergeForm.isPullBranchDeletable && !autoMergeWhenSucceed">
<input name="delete_branch_after_merge" type="checkbox" v-model="deleteBranchAfterMerge" id="delete-branch-after-merge">
<label for="delete-branch-after-merge">{{ mergeForm.textDeleteBranch }}</label>
</div>
@@ -177,7 +177,7 @@ export default {
</div>
<!-- the cancel auto merge button -->
- <form v-if="mergeForm.hasPendingPullRequestMerge" :action="mergeForm.baseLink+'/cancel_auto_merge'" method="post" class="gt-ml-4">
+ <form v-if="mergeForm.hasPendingPullRequestMerge" :action="mergeForm.baseLink+'/cancel_auto_merge'" method="post" class="tw-ml-4">
<input type="hidden" name="_csrf" :value="csrfToken">
<button class="ui button">
{{ mergeForm.textAutoMergeCancelSchedule }}
diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue
index 02e1dcd4df..f10694cde0 100644
--- a/web_src/js/components/RepoActionView.vue
+++ b/web_src/js/components/RepoActionView.vue
@@ -382,7 +382,7 @@ export function initRepositoryActionView() {
<button class="ui basic small compact button red" @click="cancelRun()" v-else-if="run.canCancel">
{{ locale.cancel }}
</button>
- <button class="ui basic small compact button gt-mr-0 link-action" :data-url="`${run.link}/rerun`" v-else-if="run.canRerun">
+ <button class="ui basic small compact button tw-mr-0 link-action" :data-url="`${run.link}/rerun`" v-else-if="run.canRerun">
{{ locale.rerun_all }}
</button>
</div>
@@ -407,10 +407,10 @@ export function initRepositoryActionView() {
<a class="job-brief-item" :href="run.link+'/jobs/'+index" :class="parseInt(jobIndex) === index ? 'selected' : ''" v-for="(job, index) in run.jobs" :key="job.id" @mouseenter="onHoverRerunIndex = job.id" @mouseleave="onHoverRerunIndex = -1">
<div class="job-brief-item-left">
<ActionRunStatus :locale-status="locale.status[job.status]" :status="job.status"/>
- <span class="job-brief-name gt-mx-3 gt-ellipsis">{{ job.name }}</span>
+ <span class="job-brief-name tw-mx-2 gt-ellipsis">{{ job.name }}</span>
</div>
<span class="job-brief-item-right">
- <SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-brief-rerun gt-mx-3 link-action" :data-url="`${run.link}/jobs/${index}/rerun`" v-if="job.canRerun && onHoverRerunIndex === job.id"/>
+ <SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-brief-rerun tw-mx-2 link-action" :data-url="`${run.link}/jobs/${index}/rerun`" v-if="job.canRerun && onHoverRerunIndex === job.id"/>
<span class="step-summary-duration">{{ job.duration }}</span>
</span>
</a>
@@ -445,7 +445,7 @@ export function initRepositoryActionView() {
</div>
<div class="job-info-header-right">
<div class="ui top right pointing dropdown custom jump item" @click.stop="menuVisible = !menuVisible" @keyup.enter="menuVisible = !menuVisible">
- <button class="btn gt-interact-bg gt-p-3">
+ <button class="btn gt-interact-bg tw-p-2">
<SvgIcon name="octicon-gear" :size="18"/>
</button>
<div class="menu transition action-job-menu" :class="{visible: menuVisible}" v-if="menuVisible" v-cloak>
@@ -462,7 +462,7 @@ export function initRepositoryActionView() {
{{ locale.showFullScreen }}
</a>
<div class="divider"/>
- <a :class="['item', currentJob.steps.length === 0 ? 'disabled' : '']" :href="run.link+'/jobs/'+jobIndex+'/logs'" target="_blank">
+ <a :class="['item', !currentJob.steps.length ? 'disabled' : '']" :href="run.link+'/jobs/'+jobIndex+'/logs'" target="_blank">
<i class="icon"><SvgIcon name="octicon-download"/></i>
{{ locale.downloadLogs }}
</a>
@@ -472,13 +472,13 @@ export function initRepositoryActionView() {
</div>
<div class="job-step-container" ref="steps" v-if="currentJob.steps.length">
<div class="job-step-section" v-for="(jobStep, i) in currentJob.steps" :key="i">
- <div class="job-step-summary" @click.stop="jobStep.status !== 'skipped' && toggleStepLogs(i)" :class="[currentJobStepsStates[i].expanded ? 'selected' : '', isExpandable(jobStep.status) && 'step-expandable']">
+ <div class="job-step-summary" @click.stop="isExpandable(jobStep.status) && toggleStepLogs(i)" :class="[currentJobStepsStates[i].expanded ? 'selected' : '', isExpandable(jobStep.status) && 'step-expandable']">
<!-- If the job is done and the job step log is loaded for the first time, show the loading icon
currentJobStepsStates[i].cursor === null means the log is loaded for the first time
-->
- <SvgIcon v-if="isDone(run.status) && currentJobStepsStates[i].expanded && currentJobStepsStates[i].cursor === null" name="octicon-sync" class="gt-mr-3 job-status-rotate"/>
- <SvgIcon v-else :name="currentJobStepsStates[i].expanded ? 'octicon-chevron-down': 'octicon-chevron-right'" :class="['gt-mr-3', !isExpandable(jobStep.status) && 'tw-invisible']"/>
- <ActionRunStatus :status="jobStep.status" class="gt-mr-3"/>
+ <SvgIcon v-if="isDone(run.status) && currentJobStepsStates[i].expanded && currentJobStepsStates[i].cursor === null" name="octicon-sync" class="tw-mr-2 job-status-rotate"/>
+ <SvgIcon v-else :name="currentJobStepsStates[i].expanded ? 'octicon-chevron-down': 'octicon-chevron-right'" :class="['tw-mr-2', !isExpandable(jobStep.status) && 'tw-invisible']"/>
+ <ActionRunStatus :status="jobStep.status" class="tw-mr-2"/>
<span class="step-summary-msg gt-ellipsis">{{ jobStep.summary }}</span>
<span class="step-summary-duration">{{ jobStep.duration }}</span>
@@ -637,6 +637,8 @@ export function initRepositoryActionView() {
flex-direction: column;
border: 1px solid var(--color-console-border);
border-radius: var(--border-radius);
+ background: var(--color-console-bg);
+ align-self: flex-start;
}
/* begin fomantic button overrides */
@@ -696,10 +698,8 @@ export function initRepositoryActionView() {
justify-content: space-between;
align-items: center;
padding: 0 12px;
- background-color: var(--color-console-bg);
position: sticky;
top: 0;
- border-radius: var(--border-radius);
height: 60px;
z-index: 1;
}
@@ -720,7 +720,6 @@ export function initRepositoryActionView() {
}
.job-step-container {
- background-color: var(--color-console-bg);
max-height: 100%;
border-radius: 0 0 var(--border-radius) var(--border-radius);
border-top: 1px solid var(--color-console-border);
diff --git a/web_src/js/components/RepoBranchTagSelector.vue b/web_src/js/components/RepoBranchTagSelector.vue
index 34e8859609..4e977ab185 100644
--- a/web_src/js/components/RepoBranchTagSelector.vue
+++ b/web_src/js/components/RepoBranchTagSelector.vue
@@ -19,17 +19,19 @@ const sfc = {
});
// TODO: fix this anti-pattern: side-effects-in-computed-properties
- this.active = (items.length === 0 && this.showCreateNewBranch ? 0 : -1);
+ this.active = !items.length && this.showCreateNewBranch ? 0 : -1;
return items;
},
showNoResults() {
- return this.filteredItems.length === 0 && !this.showCreateNewBranch;
+ return !this.filteredItems.length && !this.showCreateNewBranch;
},
showCreateNewBranch() {
if (this.disableCreateBranch || !this.searchTerm) {
return false;
}
- return this.items.filter((item) => item.name.toLowerCase() === this.searchTerm.toLowerCase()).length === 0;
+ return !this.items.filter((item) => {
+ return item.name.toLowerCase() === this.searchTerm.toLowerCase();
+ }).length;
},
formActionUrl() {
return `${this.repoLink}/branches/_new/${this.branchNameSubURL}`;
@@ -245,13 +247,13 @@ export default sfc; // activate IDE's Vue plugin
</script>
<template>
<div class="ui dropdown custom">
- <button class="branch-dropdown-button gt-ellipsis ui basic small compact button tw-flex gt-m-0" @click="menuVisible = !menuVisible" @keyup.enter="menuVisible = !menuVisible">
- <span class="text tw-flex tw-items-center gt-mr-2">
+ <button class="branch-dropdown-button gt-ellipsis ui basic small compact button tw-flex tw-m-0" @click="menuVisible = !menuVisible" @keyup.enter="menuVisible = !menuVisible">
+ <span class="text tw-flex tw-items-center tw-mr-1">
<template v-if="release">{{ textReleaseCompare }}</template>
<template v-else>
<svg-icon v-if="isViewTag" name="octicon-tag"/>
<svg-icon v-else name="octicon-git-branch"/>
- <strong ref="dropdownRefName" class="gt-ml-3">{{ refNameText }}</strong>
+ <strong ref="dropdownRefName" class="tw-ml-2">{{ refNameText }}</strong>
</template>
</span>
<svg-icon name="octicon-triangle-down" :size="14" class-name="dropdown icon"/>
@@ -263,10 +265,10 @@ export default sfc; // activate IDE's Vue plugin
</div>
<div v-if="showBranchesInDropdown" class="branch-tag-tab">
<a class="branch-tag-item muted" :class="{active: mode === 'branches'}" href="#" @click="handleTabSwitch('branches')">
- <svg-icon name="octicon-git-branch" :size="16" class-name="gt-mr-2"/>{{ textBranches }}
+ <svg-icon name="octicon-git-branch" :size="16" class-name="tw-mr-1"/>{{ textBranches }}
</a>
<a v-if="!noTag" class="branch-tag-item muted" :class="{active: mode === 'tags'}" href="#" @click="handleTabSwitch('tags')">
- <svg-icon name="octicon-tag" :size="16" class-name="gt-mr-2"/>{{ textTags }}
+ <svg-icon name="octicon-tag" :size="16" class-name="tw-mr-1"/>{{ textTags }}
</a>
</div>
<div class="branch-tag-divider"/>
diff --git a/web_src/js/components/RepoCodeFrequency.vue b/web_src/js/components/RepoCodeFrequency.vue
index f51dac0a6d..adce431264 100644
--- a/web_src/js/components/RepoCodeFrequency.vue
+++ b/web_src/js/components/RepoCodeFrequency.vue
@@ -150,7 +150,7 @@ export default {
<div class="tw-flex ui segment main-graph">
<div v-if="isLoading || errorText !== ''" class="gt-tc tw-m-auto">
<div v-if="isLoading">
- <SvgIcon name="octicon-sync" class="gt-mr-3 job-status-rotate"/>
+ <SvgIcon name="octicon-sync" class="tw-mr-2 job-status-rotate"/>
{{ locale.loadingInfo }}
</div>
<div v-else class="text red">
diff --git a/web_src/js/components/RepoContributors.vue b/web_src/js/components/RepoContributors.vue
index 02db9e3e3e..2347c41ae4 100644
--- a/web_src/js/components/RepoContributors.vue
+++ b/web_src/js/components/RepoContributors.vue
@@ -355,7 +355,7 @@ export default {
<div class="tw-flex ui segment main-graph">
<div v-if="isLoading || errorText !== ''" class="gt-tc tw-m-auto">
<div v-if="isLoading">
- <SvgIcon name="octicon-sync" class="gt-mr-3 job-status-rotate"/>
+ <SvgIcon name="octicon-sync" class="tw-mr-2 job-status-rotate"/>
{{ locale.loadingInfo }}
</div>
<div v-else class="text red">
@@ -379,7 +379,7 @@ export default {
<a :href="contributor.home_link">
<img class="ui avatar tw-align-middle" height="40" width="40" :src="contributor.avatar_link">
</a>
- <div class="gt-ml-3">
+ <div class="tw-ml-2">
<a v-if="contributor.home_link !== ''" :href="contributor.home_link"><h4>{{ contributor.name }}</h4></a>
<h4 v-else class="contributor-name">
{{ contributor.name }}
diff --git a/web_src/js/components/RepoRecentCommits.vue b/web_src/js/components/RepoRecentCommits.vue
index 601252419a..502af533da 100644
--- a/web_src/js/components/RepoRecentCommits.vue
+++ b/web_src/js/components/RepoRecentCommits.vue
@@ -127,7 +127,7 @@ export default {
<div class="tw-flex ui segment main-graph">
<div v-if="isLoading || errorText !== ''" class="gt-tc tw-m-auto">
<div v-if="isLoading">
- <SvgIcon name="octicon-sync" class="gt-mr-3 job-status-rotate"/>
+ <SvgIcon name="octicon-sync" class="tw-mr-2 job-status-rotate"/>
{{ locale.loadingInfo }}
</div>
<div v-else class="text red">
diff --git a/web_src/js/components/ScopedAccessTokenSelector.vue b/web_src/js/components/ScopedAccessTokenSelector.vue
index ae4e8299f2..103cc525ad 100644
--- a/web_src/js/components/ScopedAccessTokenSelector.vue
+++ b/web_src/js/components/ScopedAccessTokenSelector.vue
@@ -87,7 +87,7 @@ export function initScopedAccessTokenCategories() {
</script>
<template>
- <div v-for="category in categories" :key="category" class="field gt-pl-2 gt-pb-2 access-token-category">
+ <div v-for="category in categories" :key="category" class="field tw-pl-1 tw-pb-1 access-token-category">
<label class="category-label" :for="'access-token-scope-' + category">
{{ category }}
</label>
diff --git a/web_src/js/features/admin/common.js b/web_src/js/features/admin/common.js
index 0c65f04ab8..8a88996742 100644
--- a/web_src/js/features/admin/common.js
+++ b/web_src/js/features/admin/common.js
@@ -5,138 +5,165 @@ import {POST} from '../../modules/fetch.js';
const {appSubUrl} = window.config;
-export function initAdminCommon() {
- if ($('.page-content.admin').length === 0) {
- return;
+function onSecurityProtocolChange() {
+ if (Number(document.getElementById('security_protocol')?.value) > 0) {
+ showElem('.has-tls');
+ } else {
+ hideElem('.has-tls');
}
+}
+
+export function initAdminCommon() {
+ if (!document.querySelector('.page-content.admin')) return;
// check whether appUrl(ROOT_URL) is correct, if not, show an error message
checkAppUrl();
// New user
if ($('.admin.new.user').length > 0 || $('.admin.edit.user').length > 0) {
- $('#login_type').on('change', function () {
- if ($(this).val().substring(0, 1) === '0') {
- $('#user_name').removeAttr('disabled');
- $('#login_name').removeAttr('required');
- hideElem($('.non-local'));
- showElem($('.local'));
- $('#user_name').trigger('focus');
-
- if ($(this).data('password') === 'required') {
- $('#password').attr('required', 'required');
+ document.getElementById('login_type')?.addEventListener('change', function () {
+ if (this.value?.substring(0, 1) === '0') {
+ document.getElementById('user_name')?.removeAttribute('disabled');
+ document.getElementById('login_name')?.removeAttribute('required');
+ hideElem('.non-local');
+ showElem('.local');
+ document.getElementById('user_name')?.focus();
+
+ if (this.getAttribute('data-password') === 'required') {
+ document.getElementById('password')?.setAttribute('required', 'required');
}
} else {
- if ($('.admin.edit.user').length > 0) {
- $('#user_name').attr('disabled', 'disabled');
+ if (document.querySelector('.admin.edit.user')) {
+ document.getElementById('user_name')?.setAttribute('disabled', 'disabled');
}
- $('#login_name').attr('required', 'required');
- showElem($('.non-local'));
- hideElem($('.local'));
- $('#login_name').trigger('focus');
+ document.getElementById('login_name')?.setAttribute('required', 'required');
+ showElem('.non-local');
+ hideElem('.local');
+ document.getElementById('login_name')?.focus();
- $('#password').removeAttr('required');
+ document.getElementById('password')?.removeAttribute('required');
}
});
}
- function onSecurityProtocolChange() {
- if ($('#security_protocol').val() > 0) {
- showElem($('.has-tls'));
- } else {
- hideElem($('.has-tls'));
- }
- }
-
function onUsePagedSearchChange() {
+ const searchPageSizeElements = document.querySelectorAll('.search-page-size');
if (document.getElementById('use_paged_search').checked) {
showElem('.search-page-size');
- $('.search-page-size').find('input').attr('required', 'required');
+ for (const el of searchPageSizeElements) {
+ el.querySelector('input')?.setAttribute('required', 'required');
+ }
} else {
hideElem('.search-page-size');
- $('.search-page-size').find('input').removeAttr('required');
+ for (const el of searchPageSizeElements) {
+ el.querySelector('input')?.removeAttribute('required');
+ }
}
}
function onOAuth2Change(applyDefaultValues) {
- hideElem($('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url'));
- $('.open_id_connect_auto_discovery_url input[required]').removeAttr('required');
+ hideElem('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url');
+ for (const input of document.querySelectorAll('.open_id_connect_auto_discovery_url input[required]')) {
+ input.removeAttribute('required');
+ }
- const provider = $('#oauth2_provider').val();
+ const provider = document.getElementById('oauth2_provider')?.value;
switch (provider) {
case 'openidConnect':
- $('.open_id_connect_auto_discovery_url input').attr('required', 'required');
- showElem($('.open_id_connect_auto_discovery_url'));
+ for (const input of document.querySelectorAll('.open_id_connect_auto_discovery_url input')) {
+ input.setAttribute('required', 'required');
+ }
+ showElem('.open_id_connect_auto_discovery_url');
break;
default:
- if ($(`#${provider}_customURLSettings`).data('required')) {
- $('#oauth2_use_custom_url').attr('checked', 'checked');
+ if (document.getElementById(`#${provider}_customURLSettings`)?.getAttribute('data-required')) {
+ document.getElementById('oauth2_use_custom_url')?.setAttribute('checked', 'checked');
}
- if ($(`#${provider}_customURLSettings`).data('available')) {
- showElem($('.oauth2_use_custom_url'));
+ if (document.getElementById(`#${provider}_customURLSettings`)?.getAttribute('data-available')) {
+ showElem('.oauth2_use_custom_url');
}
}
onOAuth2UseCustomURLChange(applyDefaultValues);
}
function onOAuth2UseCustomURLChange(applyDefaultValues) {
- const provider = $('#oauth2_provider').val();
- hideElem($('.oauth2_use_custom_url_field'));
- $('.oauth2_use_custom_url_field input[required]').removeAttr('required');
+ const provider = document.getElementById('oauth2_provider')?.value;
+ hideElem('.oauth2_use_custom_url_field');
+ for (const input of document.querySelectorAll('.oauth2_use_custom_url_field input[required]')) {
+ input.removeAttribute('required');
+ }
- if ($('#oauth2_use_custom_url').is(':checked')) {
+ if (document.getElementById('oauth2_use_custom_url')?.checked) {
for (const custom of ['token_url', 'auth_url', 'profile_url', 'email_url', 'tenant']) {
if (applyDefaultValues) {
- $(`#oauth2_${custom}`).val($(`#${provider}_${custom}`).val());
+ document.getElementById(`oauth2_${custom}`).value = document.getElementById(`${provider}_${custom}`).value;
}
- if ($(`#${provider}_${custom}`).data('available')) {
- $(`.oauth2_${custom} input`).attr('required', 'required');
- showElem($(`.oauth2_${custom}`));
+ const customInput = document.getElementById(`${provider}_${custom}`);
+ if (customInput && customInput.getAttribute('data-available')) {
+ for (const input of document.querySelectorAll(`.oauth2_${custom} input`)) {
+ input.setAttribute('required', 'required');
+ }
+ showElem(`.oauth2_${custom}`);
}
}
}
}
function onEnableLdapGroupsChange() {
- toggleElem($('#ldap-group-options'), $('.js-ldap-group-toggle').is(':checked'));
+ toggleElem(document.getElementById('ldap-group-options'), $('.js-ldap-group-toggle')[0].checked);
}
// New authentication
- if ($('.admin.new.authentication').length > 0) {
- $('#auth_type').on('change', function () {
- hideElem($('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi'));
+ if (document.querySelector('.admin.new.authentication')) {
+ document.getElementById('auth_type')?.addEventListener('change', function () {
+ hideElem('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi');
+
+ for (const input of document.querySelectorAll('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required], .sspi input[required]')) {
+ input.removeAttribute('required');
+ }
- $('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required], .sspi input[required]').removeAttr('required');
- $('.binddnrequired').removeClass('required');
+ document.querySelector('.binddnrequired')?.classList.remove('required');
- const authType = $(this).val();
+ const authType = this.value;
switch (authType) {
case '2': // LDAP
- showElem($('.ldap'));
- $('.binddnrequired input, .ldap div.required:not(.dldap) input').attr('required', 'required');
- $('.binddnrequired').addClass('required');
+ showElem('.ldap');
+ for (const input of document.querySelectorAll('.binddnrequired input, .ldap div.required:not(.dldap) input')) {
+ input.setAttribute('required', 'required');
+ }
+ document.querySelector('.binddnrequired')?.classList.add('required');
break;
case '3': // SMTP
- showElem($('.smtp'));
- showElem($('.has-tls'));
- $('.smtp div.required input, .has-tls').attr('required', 'required');
+ showElem('.smtp');
+ showElem('.has-tls');
+ for (const input of document.querySelectorAll('.smtp div.required input, .has-tls')) {
+ input.setAttribute('required', 'required');
+ }
break;
case '4': // PAM
- showElem($('.pam'));
- $('.pam input').attr('required', 'required');
+ showElem('.pam');
+ for (const input of document.querySelectorAll('.pam input')) {
+ input.setAttribute('required', 'required');
+ }
break;
case '5': // LDAP
- showElem($('.dldap'));
- $('.dldap div.required:not(.ldap) input').attr('required', 'required');
+ showElem('.dldap');
+ for (const input of document.querySelectorAll('.dldap div.required:not(.ldap) input')) {
+ input.setAttribute('required', 'required');
+ }
break;
case '6': // OAuth2
- showElem($('.oauth2'));
- $('.oauth2 div.required:not(.oauth2_use_custom_url,.oauth2_use_custom_url_field,.open_id_connect_auto_discovery_url) input').attr('required', 'required');
+ showElem('.oauth2');
+ for (const input of document.querySelectorAll('.oauth2 div.required:not(.oauth2_use_custom_url,.oauth2_use_custom_url_field,.open_id_connect_auto_discovery_url) input')) {
+ input.setAttribute('required', 'required');
+ }
onOAuth2Change(true);
break;
case '7': // SSPI
- showElem($('.sspi'));
- $('.sspi div.required input').attr('required', 'required');
+ showElem('.sspi');
+ for (const input of document.querySelectorAll('.sspi div.required input')) {
+ input.setAttribute('required', 'required');
+ }
break;
}
if (authType === '2' || authType === '5') {
@@ -148,44 +175,44 @@ export function initAdminCommon() {
}
});
$('#auth_type').trigger('change');
- $('#security_protocol').on('change', onSecurityProtocolChange);
- $('#use_paged_search').on('change', onUsePagedSearchChange);
- $('#oauth2_provider').on('change', () => onOAuth2Change(true));
- $('#oauth2_use_custom_url').on('change', () => onOAuth2UseCustomURLChange(true));
+ document.getElementById('security_protocol')?.addEventListener('change', onSecurityProtocolChange);
+ document.getElementById('use_paged_search')?.addEventListener('change', onUsePagedSearchChange);
+ document.getElementById('oauth2_provider')?.addEventListener('change', () => onOAuth2Change(true));
+ document.getElementById('oauth2_use_custom_url')?.addEventListener('change', () => onOAuth2UseCustomURLChange(true));
$('.js-ldap-group-toggle').on('change', onEnableLdapGroupsChange);
}
// Edit authentication
- if ($('.admin.edit.authentication').length > 0) {
- const authType = $('#auth_type').val();
+ if (document.querySelector('.admin.edit.authentication')) {
+ const authType = document.getElementById('auth_type')?.value;
if (authType === '2' || authType === '5') {
- $('#security_protocol').on('change', onSecurityProtocolChange);
+ document.getElementById('security_protocol')?.addEventListener('change', onSecurityProtocolChange);
$('.js-ldap-group-toggle').on('change', onEnableLdapGroupsChange);
onEnableLdapGroupsChange();
if (authType === '2') {
- $('#use_paged_search').on('change', onUsePagedSearchChange);
+ document.getElementById('use_paged_search')?.addEventListener('change', onUsePagedSearchChange);
}
} else if (authType === '6') {
- $('#oauth2_provider').on('change', () => onOAuth2Change(true));
- $('#oauth2_use_custom_url').on('change', () => onOAuth2UseCustomURLChange(false));
+ document.getElementById('oauth2_provider')?.addEventListener('change', () => onOAuth2Change(true));
+ document.getElementById('oauth2_use_custom_url')?.addEventListener('change', () => onOAuth2UseCustomURLChange(false));
onOAuth2Change(false);
}
}
- if ($('.admin.authentication').length > 0) {
+ if (document.querySelector('.admin.authentication')) {
$('#auth_name').on('input', function () {
// appSubUrl is either empty or is a path that starts with `/` and doesn't have a trailing slash.
- $('#oauth2-callback-url').text(`${window.location.origin}${appSubUrl}/user/oauth2/${encodeURIComponent($(this).val())}/callback`);
+ document.getElementById('oauth2-callback-url').textContent = `${window.location.origin}${appSubUrl}/user/oauth2/${encodeURIComponent(this.value)}/callback`;
}).trigger('input');
}
// Notice
- if ($('.admin.notice')) {
- const $detailModal = $('#detail-modal');
+ if (document.querySelector('.admin.notice')) {
+ const $detailModal = document.getElementById('detail-modal');
// Attach view detail modals
$('.view-detail').on('click', function () {
$detailModal.find('.content pre').text($(this).parents('tr').find('.notice-description').text());
- $detailModal.find('.sub.header').text($(this).parents('tr').find('relative-time').attr('title'));
+ $detailModal.find('.sub.header').text(this.closest('tr')?.querySelector('relative-time')?.getAttribute('title'));
$detailModal.modal('show');
return false;
});
@@ -205,18 +232,17 @@ export function initAdminCommon() {
break;
}
});
- $('#delete-selection').on('click', async function (e) {
+ document.getElementById('delete-selection')?.addEventListener('click', async function (e) {
e.preventDefault();
- const $this = $(this);
- $this.addClass('is-loading disabled');
+ this.classList.add('is-loading', 'disabled');
const data = new FormData();
$checkboxes.each(function () {
if ($(this).checkbox('is checked')) {
- data.append('ids[]', $(this).data('id'));
+ data.append('ids[]', this.getAttribute('data-id'));
}
});
- await POST($this.data('link'), {data});
- window.location.href = $this.data('redirect');
+ await POST(this.getAttribute('data-link'), {data});
+ window.location.href = this.getAttribute('data-redirect');
});
}
}
diff --git a/web_src/js/features/admin/users.js b/web_src/js/features/admin/users.js
index 171e50f9e9..7cac603b5c 100644
--- a/web_src/js/features/admin/users.js
+++ b/web_src/js/features/admin/users.js
@@ -1,34 +1,39 @@
-import $ from 'jquery';
-
export function initAdminUserListSearchForm() {
const searchForm = window.config.pageData.adminUserListSearchForm;
if (!searchForm) return;
- const $form = $('#user-list-search-form');
- if (!$form.length) return;
+ const form = document.querySelector('#user-list-search-form');
+ if (!form) return;
- $form.find(`button[name=sort][value=${searchForm.SortType}]`).addClass('active');
+ for (const button of form.querySelectorAll(`button[name=sort][value="${searchForm.SortType}"]`)) {
+ button.classList.add('active');
+ }
if (searchForm.StatusFilterMap) {
for (const [k, v] of Object.entries(searchForm.StatusFilterMap)) {
if (!v) continue;
- $form.find(`input[name="status_filter[${k}]"][value=${v}]`).checked = true;
+ for (const input of form.querySelectorAll(`input[name="status_filter[${k}]"][value="${v}"]`)) {
+ input.checked = true;
+ }
}
}
- $form.find(`input[type=radio]`).on('click', () => {
- $form.trigger('submit');
- return false;
- });
+ for (const radio of form.querySelectorAll('input[type=radio]')) {
+ radio.addEventListener('click', () => {
+ form.submit();
+ });
+ }
- $form.find('.j-reset-status-filter').on('click', () => {
- $form.find(`input[type=radio]`).each((_, e) => {
- const $e = $(e);
- if ($e.attr('name').startsWith('status_filter[')) {
- $e.checked = false;
+ const resetButtons = form.querySelectorAll('.j-reset-status-filter');
+ for (const button of resetButtons) {
+ button.addEventListener('click', (e) => {
+ e.preventDefault();
+ for (const input of form.querySelectorAll('input[type=radio]')) {
+ if (input.name.startsWith('status_filter[')) {
+ input.checked = false;
+ }
}
+ form.submit();
});
- $form.trigger('submit');
- return false;
- });
+ }
}
diff --git a/web_src/js/features/autofocus-end.js b/web_src/js/features/autofocus-end.js
new file mode 100644
index 0000000000..da71ce9536
--- /dev/null
+++ b/web_src/js/features/autofocus-end.js
@@ -0,0 +1,6 @@
+export function initAutoFocusEnd() {
+ for (const el of document.querySelectorAll('.js-autofocus-end')) {
+ el.focus(); // expects only one such element on one page. If there are many, then the last one gets the focus.
+ el.setSelectionRange(el.value.length, el.value.length);
+ }
+}
diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js
index e27935a86e..18849ba7c1 100644
--- a/web_src/js/features/common-global.js
+++ b/web_src/js/features/common-global.js
@@ -19,7 +19,7 @@ const {appUrl, appSubUrl, csrfToken, i18n} = window.config;
export function initGlobalFormDirtyLeaveConfirm() {
// Warn users that try to leave a page after entering data into a form.
// Except on sign-in pages, and for forms marked as 'ignore-dirty'.
- if ($('.user.signin').length === 0) {
+ if (!$('.user.signin').length) {
$('form:not(.ignore-dirty)').areYouSure();
}
}
@@ -373,7 +373,7 @@ function initGlobalShowModal() {
if (attrTargetAttr) {
$attrTarget[0][attrTargetAttr] = attrib.value;
- } else if ($attrTarget.is('input') || $attrTarget.is('textarea')) {
+ } else if ($attrTarget[0].matches('input, textarea')) {
$attrTarget.val(attrib.value); // FIXME: add more supports like checkbox
} else {
$attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p
diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js
index 1e7b554b98..d3fab375a9 100644
--- a/web_src/js/features/comp/ComboMarkdownEditor.js
+++ b/web_src/js/features/comp/ComboMarkdownEditor.js
@@ -105,7 +105,7 @@ class ComboMarkdownEditor {
e.preventDefault();
const enabled = localStorage?.getItem('markdown-editor-monospace') !== 'true';
localStorage.setItem('markdown-editor-monospace', String(enabled));
- this.textarea.classList.toggle('gt-mono', enabled);
+ this.textarea.classList.toggle('tw-font-mono', enabled);
const text = monospaceButton.getAttribute(enabled ? 'data-disable-text' : 'data-enable-text');
monospaceButton.setAttribute('data-tooltip-content', text);
monospaceButton.setAttribute('aria-checked', String(enabled));
@@ -132,34 +132,33 @@ class ComboMarkdownEditor {
setupTab() {
const $container = $(this.container);
- const $tabMenu = $container.find('.tabular.menu');
- const $tabs = $tabMenu.find('> .item');
+ const tabs = $container[0].querySelectorAll('.tabular.menu > .item');
// Fomantic Tab requires the "data-tab" to be globally unique.
// So here it uses our defined "data-tab-for" and "data-tab-panel" to generate the "data-tab" attribute for Fomantic.
- const $tabEditor = $tabs.filter(`.item[data-tab-for="markdown-writer"]`);
- const $tabPreviewer = $tabs.filter(`.item[data-tab-for="markdown-previewer"]`);
- $tabEditor.attr('data-tab', `markdown-writer-${elementIdCounter}`);
- $tabPreviewer.attr('data-tab', `markdown-previewer-${elementIdCounter}`);
- const $panelEditor = $container.find('.ui.tab[data-tab-panel="markdown-writer"]');
- const $panelPreviewer = $container.find('.ui.tab[data-tab-panel="markdown-previewer"]');
- $panelEditor.attr('data-tab', `markdown-writer-${elementIdCounter}`);
- $panelPreviewer.attr('data-tab', `markdown-previewer-${elementIdCounter}`);
+ const tabEditor = Array.from(tabs).find((tab) => tab.getAttribute('data-tab-for') === 'markdown-writer');
+ const tabPreviewer = Array.from(tabs).find((tab) => tab.getAttribute('data-tab-for') === 'markdown-previewer');
+ tabEditor.setAttribute('data-tab', `markdown-writer-${elementIdCounter}`);
+ tabPreviewer.setAttribute('data-tab', `markdown-previewer-${elementIdCounter}`);
+ const panelEditor = $container[0].querySelector('.ui.tab[data-tab-panel="markdown-writer"]');
+ const panelPreviewer = $container[0].querySelector('.ui.tab[data-tab-panel="markdown-previewer"]');
+ panelEditor.setAttribute('data-tab', `markdown-writer-${elementIdCounter}`);
+ panelPreviewer.setAttribute('data-tab', `markdown-previewer-${elementIdCounter}`);
elementIdCounter++;
- $tabEditor[0].addEventListener('click', () => {
+ tabEditor.addEventListener('click', () => {
requestAnimationFrame(() => {
this.focus();
});
});
- $tabs.tab();
+ $(tabs).tab();
- this.previewUrl = $tabPreviewer.attr('data-preview-url');
- this.previewContext = $tabPreviewer.attr('data-preview-context');
+ this.previewUrl = tabPreviewer.getAttribute('data-preview-url');
+ this.previewContext = tabPreviewer.getAttribute('data-preview-context');
this.previewMode = this.options.previewMode ?? 'comment';
this.previewWiki = this.options.previewWiki ?? false;
- $tabPreviewer.on('click', async () => {
+ tabPreviewer.addEventListener('click', async () => {
const formData = new FormData();
formData.append('mode', this.previewMode);
formData.append('context', this.previewContext);
@@ -167,7 +166,7 @@ class ComboMarkdownEditor {
formData.append('wiki', this.previewWiki);
const response = await POST(this.previewUrl, {data: formData});
const data = await response.text();
- renderPreviewPanelContent($panelPreviewer, data);
+ renderPreviewPanelContent($(panelPreviewer), data);
});
}
diff --git a/web_src/js/features/comp/LabelEdit.js b/web_src/js/features/comp/LabelEdit.js
index 44fc9d9b6b..843657a6b6 100644
--- a/web_src/js/features/comp/LabelEdit.js
+++ b/web_src/js/features/comp/LabelEdit.js
@@ -6,23 +6,23 @@ function isExclusiveScopeName(name) {
}
function updateExclusiveLabelEdit(form) {
- const $nameInput = $(`${form} .label-name-input`);
- const $exclusiveField = $(`${form} .label-exclusive-input-field`);
- const $exclusiveCheckbox = $(`${form} .label-exclusive-input`);
- const $exclusiveWarning = $(`${form} .label-exclusive-warning`);
+ const nameInput = document.querySelector(`${form} .label-name-input`);
+ const exclusiveField = document.querySelector(`${form} .label-exclusive-input-field`);
+ const exclusiveCheckbox = document.querySelector(`${form} .label-exclusive-input`);
+ const exclusiveWarning = document.querySelector(`${form} .label-exclusive-warning`);
- if (isExclusiveScopeName($nameInput.val())) {
- $exclusiveField.removeClass('muted');
- $exclusiveField.removeAttr('aria-disabled');
- if ($exclusiveCheckbox[0].checked && $exclusiveCheckbox.data('exclusive-warn')) {
- $exclusiveWarning.removeClass('gt-hidden');
+ if (isExclusiveScopeName(nameInput.value)) {
+ exclusiveField?.classList.remove('muted');
+ exclusiveField?.removeAttribute('aria-disabled');
+ if (exclusiveCheckbox.checked && exclusiveCheckbox.getAttribute('data-exclusive-warn')) {
+ exclusiveWarning?.classList.remove('tw-hidden');
} else {
- $exclusiveWarning.addClass('gt-hidden');
+ exclusiveWarning?.classList.add('tw-hidden');
}
} else {
- $exclusiveField.addClass('muted');
- $exclusiveField.attr('aria-disabled', 'true');
- $exclusiveWarning.addClass('gt-hidden');
+ exclusiveField?.classList.add('muted');
+ exclusiveField?.setAttribute('aria-disabled', 'true');
+ exclusiveWarning?.classList.add('tw-hidden');
}
}
diff --git a/web_src/js/features/comp/ReactionSelector.js b/web_src/js/features/comp/ReactionSelector.js
index 6df4bde069..2def3db51a 100644
--- a/web_src/js/features/comp/ReactionSelector.js
+++ b/web_src/js/features/comp/ReactionSelector.js
@@ -5,11 +5,11 @@ export function initCompReactionSelector($parent) {
$parent.find(`.select-reaction .item.reaction, .comment-reaction-button`).on('click', async function (e) {
e.preventDefault();
- if ($(this).hasClass('disabled')) return;
+ if (this.classList.contains('disabled')) return;
- const actionUrl = $(this).closest('[data-action-url]').attr('data-action-url');
- const reactionContent = $(this).attr('data-reaction-content');
- const hasReacted = $(this).closest('.ui.segment.reactions').find(`a[data-reaction-content="${reactionContent}"]`).attr('data-has-reacted') === 'true';
+ const actionUrl = this.closest('[data-action-url]')?.getAttribute('data-action-url');
+ const reactionContent = this.getAttribute('data-reaction-content');
+ const hasReacted = this.closest('.ui.segment.reactions')?.querySelector(`a[data-reaction-content="${reactionContent}"]`)?.getAttribute('data-has-reacted') === 'true';
const res = await POST(`${actionUrl}/${hasReacted ? 'unreact' : 'react'}`, {
data: new URLSearchParams({content: reactionContent}),
diff --git a/web_src/js/features/comp/SearchUserBox.js b/web_src/js/features/comp/SearchUserBox.js
index 83d7044f11..081c47425f 100644
--- a/web_src/js/features/comp/SearchUserBox.js
+++ b/web_src/js/features/comp/SearchUserBox.js
@@ -34,7 +34,7 @@ export function initCompSearchUserBox() {
}
});
- if (allowEmailInput && items.length === 0 && looksLikeEmailAddressCheck.test(searchQuery)) {
+ if (allowEmailInput && !items.length && looksLikeEmailAddressCheck.test(searchQuery)) {
const resultItem = {
title: searchQuery,
description: allowEmailDescription,
diff --git a/web_src/js/features/notification.js b/web_src/js/features/notification.js
index a460c5d333..0a7ffe7afa 100644
--- a/web_src/js/features/notification.js
+++ b/web_src/js/features/notification.js
@@ -35,7 +35,7 @@ async function receiveUpdateCount(event) {
const data = JSON.parse(event.data);
for (const count of document.querySelectorAll('.notification_count')) {
- count.classList.toggle('gt-hidden', data.Count === 0);
+ count.classList.toggle('tw-hidden', data.Count === 0);
count.textContent = `${data.Count}`;
}
await updateNotificationTable();
@@ -179,9 +179,9 @@ async function updateNotificationCount() {
const $notificationCount = $('.notification_count');
if (data.new === 0) {
- $notificationCount.addClass('gt-hidden');
+ $notificationCount.addClass('tw-hidden');
} else {
- $notificationCount.removeClass('gt-hidden');
+ $notificationCount.removeClass('tw-hidden');
}
$notificationCount.text(`${data.new}`);
diff --git a/web_src/js/features/org-team.js b/web_src/js/features/org-team.js
index 2236bc58bc..c216fdf6a2 100644
--- a/web_src/js/features/org-team.js
+++ b/web_src/js/features/org-team.js
@@ -8,9 +8,9 @@ export function initOrgTeamSettings() {
$('.organization.new.team input[name=permission]').on('change', () => {
const val = $('input[name=permission]:checked', '.organization.new.team').val();
if (val === 'admin') {
- hideElem($('.organization.new.team .team-units'));
+ hideElem('.organization.new.team .team-units');
} else {
- showElem($('.organization.new.team .team-units'));
+ showElem('.organization.new.team .team-units');
}
});
}
diff --git a/web_src/js/features/repo-branch.js b/web_src/js/features/repo-branch.js
index e6da9661b6..b9ffc6127f 100644
--- a/web_src/js/features/repo-branch.js
+++ b/web_src/js/features/repo-branch.js
@@ -8,35 +8,35 @@ export function initRepoBranchButton() {
function initRepoCreateBranchButton() {
// 2 pages share this code, one is the branch list page, the other is the commit view page: create branch/tag from current commit (dirty code)
- $('.show-create-branch-modal').on('click', function () {
- let modalFormName = $(this).attr('data-modal-form');
- if (!modalFormName) {
- modalFormName = '#create-branch-form';
- }
- $(modalFormName)[0].action = $(modalFormName).attr('data-base-action') + $(this).attr('data-branch-from-urlcomponent');
- let fromSpanName = $(this).attr('data-modal-from-span');
- if (!fromSpanName) {
- fromSpanName = '#modal-create-branch-from-span';
- }
+ for (const el of document.querySelectorAll('.show-create-branch-modal')) {
+ el.addEventListener('click', () => {
+ const modalFormName = el.getAttribute('data-modal-form') || '#create-branch-form';
+ const modalForm = document.querySelector(modalFormName);
+ if (!modalForm) return;
+ modalForm.action = `${modalForm.getAttribute('data-base-action')}${el.getAttribute('data-branch-from-urlcomponent')}`;
- $(fromSpanName).text($(this).attr('data-branch-from'));
- $($(this).attr('data-modal')).modal('show');
- });
+ const fromSpanName = el.getAttribute('data-modal-from-span') || '#modal-create-branch-from-span';
+ document.querySelector(fromSpanName).textContent = el.getAttribute('data-branch-from');
+
+ $(el.getAttribute('data-modal')).modal('show');
+ });
+ }
}
function initRepoRenameBranchButton() {
- $('.show-rename-branch-modal').on('click', function () {
- const target = $(this).attr('data-modal');
- const $modal = $(target);
-
- const oldBranchName = $(this).attr('data-old-branch-name');
- $modal.find('input[name=from]').val(oldBranchName);
+ for (const el of document.querySelectorAll('.show-rename-branch-modal')) {
+ el.addEventListener('click', () => {
+ const target = el.getAttribute('data-modal');
+ const modal = document.querySelector(target);
+ const oldBranchName = el.getAttribute('data-old-branch-name');
+ modal.querySelector('input[name=from]').value = oldBranchName;
- // display the warning that the branch which is chosen is the default branch
- const $warn = $modal.find('.default-branch-warning');
- toggleElem($warn, $(this).attr('data-is-default-branch') === 'true');
+ // display the warning that the branch which is chosen is the default branch
+ const warn = modal.querySelector('.default-branch-warning');
+ toggleElem(warn, el.getAttribute('data-is-default-branch') === 'true');
- const $text = $modal.find('[data-rename-branch-to]');
- $text.text($text.attr('data-rename-branch-to').replace('%s', oldBranchName));
- });
+ const text = modal.querySelector('[data-rename-branch-to]');
+ text.textContent = text.getAttribute('data-rename-branch-to').replace('%s', oldBranchName);
+ });
+ }
}
diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js
index 08fae763b8..cb5afa8318 100644
--- a/web_src/js/features/repo-code.js
+++ b/web_src/js/features/repo-code.js
@@ -16,48 +16,50 @@ function changeHash(hash) {
}
}
-function selectRange($list, $select, $from) {
- $list.removeClass('active');
+function isBlame() {
+ return Boolean(document.querySelector('div.blame'));
+}
+
+function getLineEls() {
+ return document.querySelectorAll(`.code-view td.lines-code${isBlame() ? '.blame-code' : ''}`);
+}
+
+function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
+ $linesEls.closest('tr').removeClass('active');
// add hashchange to permalink
- const $refInNewIssue = $('a.ref-in-new-issue');
- const $copyPermalink = $('a.copy-line-permalink');
- const $viewGitBlame = $('a.view_git_blame');
+ const refInNewIssue = document.querySelector('a.ref-in-new-issue');
+ const copyPermalink = document.querySelector('a.copy-line-permalink');
+ const viewGitBlame = document.querySelector('a.view_git_blame');
const updateIssueHref = function (anchor) {
- if ($refInNewIssue.length === 0) {
- return;
- }
- const urlIssueNew = $refInNewIssue.attr('data-url-issue-new');
- const urlParamBodyLink = $refInNewIssue.attr('data-url-param-body-link');
+ if (!refInNewIssue) return;
+ const urlIssueNew = refInNewIssue.getAttribute('data-url-issue-new');
+ const urlParamBodyLink = refInNewIssue.getAttribute('data-url-param-body-link');
const issueContent = `${toAbsoluteUrl(urlParamBodyLink)}#${anchor}`; // the default content for issue body
- $refInNewIssue.attr('href', `${urlIssueNew}?body=${encodeURIComponent(issueContent)}`);
+ refInNewIssue.setAttribute('href', `${urlIssueNew}?body=${encodeURIComponent(issueContent)}`);
};
const updateViewGitBlameFragment = function (anchor) {
- if ($viewGitBlame.length === 0) {
- return;
- }
- let href = $viewGitBlame.attr('href');
+ if (!viewGitBlame) return;
+ let href = viewGitBlame.getAttribute('href');
href = `${href.replace(/#L\d+$|#L\d+-L\d+$/, '')}`;
if (anchor.length !== 0) {
href = `${href}#${anchor}`;
}
- $viewGitBlame.attr('href', href);
+ viewGitBlame.setAttribute('href', href);
};
- const updateCopyPermalinkUrl = function(anchor) {
- if ($copyPermalink.length === 0) {
- return;
- }
- let link = $copyPermalink.attr('data-url');
+ const updateCopyPermalinkUrl = function (anchor) {
+ if (!copyPermalink) return;
+ let link = copyPermalink.getAttribute('data-url');
link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`;
- $copyPermalink.attr('data-url', link);
+ copyPermalink.setAttribute('data-url', link);
};
- if ($from) {
- let a = parseInt($select.attr('rel').slice(1));
- let b = parseInt($from.attr('rel').slice(1));
+ if ($selectionStartEls) {
+ let a = parseInt($selectionEndEl[0].getAttribute('rel').slice(1));
+ let b = parseInt($selectionStartEls[0].getAttribute('rel').slice(1));
let c;
if (a !== b) {
if (a > b) {
@@ -69,7 +71,9 @@ function selectRange($list, $select, $from) {
for (let i = a; i <= b; i++) {
classes.push(`[rel=L${i}]`);
}
- $list.filter(classes.join(',')).addClass('active');
+ $linesEls.filter(classes.join(',')).each(function () {
+ $(this).closest('tr').addClass('active');
+ });
changeHash(`#L${a}-L${b}`);
updateIssueHref(`L${a}-L${b}`);
@@ -78,12 +82,12 @@ function selectRange($list, $select, $from) {
return;
}
}
- $select.addClass('active');
- changeHash(`#${$select.attr('rel')}`);
+ $selectionEndEl.closest('tr').addClass('active');
+ changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`);
- updateIssueHref($select.attr('rel'));
- updateViewGitBlameFragment($select.attr('rel'));
- updateCopyPermalinkUrl($select.attr('rel'));
+ updateIssueHref($selectionEndEl[0].getAttribute('rel'));
+ updateViewGitBlameFragment($selectionEndEl[0].getAttribute('rel'));
+ updateCopyPermalinkUrl($selectionEndEl[0].getAttribute('rel'));
}
function showLineButton() {
@@ -96,10 +100,10 @@ function showLineButton() {
}
// find active row and add button
- const tr = document.querySelector('.code-view td.lines-code.active').closest('tr');
- const td = tr.querySelector('td');
+ const tr = document.querySelector('.code-view tr.active');
+ const td = tr.querySelector('td.lines-num');
const btn = document.createElement('button');
- btn.classList.add('code-line-button');
+ btn.classList.add('code-line-button', 'ui', 'basic', 'button');
btn.innerHTML = svg('octicon-kebab-horizontal');
td.prepend(btn);
@@ -123,14 +127,18 @@ function showLineButton() {
export function initRepoCodeView() {
if ($('.code-view .lines-num').length > 0) {
$(document).on('click', '.lines-num span', function (e) {
- const $select = $(this);
- let $list;
- if ($('div.blame').length) {
- $list = $('.code-view td.lines-code.blame-code');
- } else {
- $list = $('.code-view td.lines-code');
+ const linesEls = getLineEls();
+ const selectedEls = Array.from(linesEls).filter((el) => {
+ return el.matches(`[rel=${this.getAttribute('id')}]`);
+ });
+
+ let from;
+ if (e.shiftKey) {
+ from = Array.from(linesEls).filter((el) => {
+ return el.closest('tr').classList.contains('active');
+ });
}
- selectRange($list, $list.filter(`[rel=${$select.attr('id')}]`), (e.shiftKey ? $list.filter('.active').eq(0) : null));
+ selectRange($(linesEls), $(selectedEls), from ? $(from) : null);
if (window.getSelection) {
window.getSelection().removeAllRanges();
@@ -138,28 +146,20 @@ export function initRepoCodeView() {
document.selection.empty();
}
- // show code view menu marker (don't show in blame page)
- if ($('div.blame').length === 0) {
- showLineButton();
- }
+ showLineButton();
});
$(window).on('hashchange', () => {
let m = window.location.hash.match(rangeAnchorRegex);
- let $list;
- if ($('div.blame').length) {
- $list = $('.code-view td.lines-code.blame-code');
- } else {
- $list = $('.code-view td.lines-code');
- }
+ const $linesEls = $(getLineEls());
let $first;
if (m) {
- $first = $list.filter(`[rel=${m[1]}]`);
+ $first = $linesEls.filter(`[rel=${m[1]}]`);
if ($first.length) {
- selectRange($list, $first, $list.filter(`[rel=${m[2]}]`));
+ selectRange($linesEls, $first, $linesEls.filter(`[rel=${m[2]}]`));
// show code view menu marker (don't show in blame page)
- if ($('div.blame').length === 0) {
+ if (!isBlame()) {
showLineButton();
}
@@ -169,12 +169,12 @@ export function initRepoCodeView() {
}
m = window.location.hash.match(singleAnchorRegex);
if (m) {
- $first = $list.filter(`[rel=L${m[2]}]`);
+ $first = $linesEls.filter(`[rel=L${m[2]}]`);
if ($first.length) {
- selectRange($list, $first);
+ selectRange($linesEls, $first);
// show code view menu marker (don't show in blame page)
- if ($('div.blame').length === 0) {
+ if (!isBlame()) {
showLineButton();
}
diff --git a/web_src/js/features/repo-common.js b/web_src/js/features/repo-common.js
index 2c5746c738..b750addb07 100644
--- a/web_src/js/features/repo-common.js
+++ b/web_src/js/features/repo-common.js
@@ -3,7 +3,7 @@ import {hideElem, showElem} from '../utils/dom.js';
import {POST} from '../modules/fetch.js';
async function getArchive($target, url, first) {
- const dropdownBtn = $target[0].closest('.ui.dropdown.button');
+ const dropdownBtn = $target[0].closest('.ui.dropdown.button') ?? $target[0].closest('.ui.dropdown.btn');
try {
dropdownBtn.classList.add('is-loading');
diff --git a/web_src/js/features/repo-diff-commit.js b/web_src/js/features/repo-diff-commit.js
index f0466f9320..aa7fc38360 100644
--- a/web_src/js/features/repo-diff-commit.js
+++ b/web_src/js/features/repo-diff-commit.js
@@ -35,7 +35,7 @@ function addBranches(area, branches, defaultBranch) {
function addLink(parent, href, text, tooltip) {
const link = document.createElement('a');
- link.classList.add('muted', 'gt-px-2');
+ link.classList.add('muted', 'tw-px-1');
link.href = href;
link.textContent = text;
if (tooltip) {
diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js
index 2feb2597e4..b704e91bda 100644
--- a/web_src/js/features/repo-diff.js
+++ b/web_src/js/features/repo-diff.js
@@ -42,8 +42,8 @@ function initRepoDiffFileViewToggle() {
$this.addClass('active');
const $target = $($this.data('toggle-selector'));
- $target.parent().children().addClass('gt-hidden');
- $target.removeClass('gt-hidden');
+ $target.parent().children().addClass('tw-hidden');
+ $target.removeClass('tw-hidden');
});
}
@@ -120,7 +120,7 @@ export function initRepoDiffConversationNav() {
// Previous/Next code review conversation
$(document).on('click', '.previous-conversation', (e) => {
const $conversation = $(e.currentTarget).closest('.comment-code-cloud');
- const $conversations = $('.comment-code-cloud:not(.gt-hidden)');
+ const $conversations = $('.comment-code-cloud:not(.tw-hidden)');
const index = $conversations.index($conversation);
const previousIndex = index > 0 ? index - 1 : $conversations.length - 1;
const $previousConversation = $conversations.eq(previousIndex);
@@ -129,7 +129,7 @@ export function initRepoDiffConversationNav() {
});
$(document).on('click', '.next-conversation', (e) => {
const $conversation = $(e.currentTarget).closest('.comment-code-cloud');
- const $conversations = $('.comment-code-cloud:not(.gt-hidden)');
+ const $conversations = $('.comment-code-cloud:not(.tw-hidden)');
const index = $conversations.index($conversation);
const nextIndex = index < $conversations.length - 1 ? index + 1 : 0;
const $nextConversation = $conversations.eq(nextIndex);
@@ -214,8 +214,7 @@ function initRepoDiffShowMore() {
export function initRepoDiffView() {
initRepoDiffConversationForm();
- const $diffFileList = $('#diff-file-list');
- if ($diffFileList.length === 0) return;
+ if (!$('#diff-file-list').length) return;
initDiffFileTree();
initDiffCommitSelect();
initRepoDiffShowMore();
diff --git a/web_src/js/features/repo-editor.js b/web_src/js/features/repo-editor.js
index 1ab0a57865..fc951750a9 100644
--- a/web_src/js/features/repo-editor.js
+++ b/web_src/js/features/repo-editor.js
@@ -39,11 +39,9 @@ function initEditPreviewTab($form) {
}
function initEditorForm() {
- if ($('.repository .edit.form').length === 0) {
- return;
- }
-
- initEditPreviewTab($('.repository .edit.form'));
+ const $form = $('.repository .edit.form');
+ if (!$form) return;
+ initEditPreviewTab($form);
}
function getCursorPosition($e) {
@@ -66,10 +64,10 @@ export function initRepoEditor() {
$('.js-quick-pull-choice-option').on('change', function () {
if ($(this).val() === 'commit-to-new-branch') {
- showElem($('.quick-pull-branch-name'));
+ showElem('.quick-pull-branch-name');
document.querySelector('.quick-pull-branch-name input').required = true;
} else {
- hideElem($('.quick-pull-branch-name'));
+ hideElem('.quick-pull-branch-name');
document.querySelector('.quick-pull-branch-name input').required = false;
}
$('#commit-button').text(this.getAttribute('button_text'));
@@ -165,7 +163,7 @@ export function initRepoEditor() {
commitButton?.addEventListener('click', (e) => {
// A modal which asks if an empty file should be committed
- if ($editArea.val().length === 0) {
+ if (!$editArea.val()) {
$('#edit-empty-content-modal').modal({
onApprove() {
$('.edit.form').trigger('submit');
diff --git a/web_src/js/features/repo-findfile.js b/web_src/js/features/repo-findfile.js
index cb03d9e803..cff5068a1e 100644
--- a/web_src/js/features/repo-findfile.js
+++ b/web_src/js/features/repo-findfile.js
@@ -77,13 +77,13 @@ function filterRepoFiles(filter) {
const filterResult = filterRepoFilesWeighted(files, filter);
- toggleElem(repoFindFileNoResult, filterResult.length === 0);
+ toggleElem(repoFindFileNoResult, !filterResult.length);
for (const r of filterResult) {
const row = document.createElement('tr');
const cell = document.createElement('td');
const a = document.createElement('a');
a.setAttribute('href', `${treeLink}/${pathEscapeSegments(r.matchResult.join(''))}`);
- a.innerHTML = svg('octicon-file', 16, 'gt-mr-3');
+ a.innerHTML = svg('octicon-file', 16, 'tw-mr-2');
row.append(cell);
cell.append(a);
for (const [index, part] of r.matchResult.entries()) {
diff --git a/web_src/js/features/repo-graph.js b/web_src/js/features/repo-graph.js
index f2c0d78f34..a5b61bff54 100644
--- a/web_src/js/features/repo-graph.js
+++ b/web_src/js/features/repo-graph.js
@@ -57,9 +57,9 @@ export function initRepoGraphGit() {
ajaxUrl.searchParams.set('div-only', 'true');
window.history.replaceState({}, '', queryString ? `?${queryString}` : window.location.pathname);
$('#pagination').empty();
- $('#rel-container').addClass('gt-hidden');
- $('#rev-container').addClass('gt-hidden');
- $('#loading-indicator').removeClass('gt-hidden');
+ $('#rel-container').addClass('tw-hidden');
+ $('#rev-container').addClass('tw-hidden');
+ $('#loading-indicator').removeClass('tw-hidden');
(async () => {
const response = await GET(String(ajaxUrl));
const html = await response.text();
@@ -67,9 +67,9 @@ export function initRepoGraphGit() {
$('#pagination').html($div.find('#pagination').html());
$('#rel-container').html($div.find('#rel-container').html());
$('#rev-container').html($div.find('#rev-container').html());
- $('#loading-indicator').addClass('gt-hidden');
- $('#rel-container').removeClass('gt-hidden');
- $('#rev-container').removeClass('gt-hidden');
+ $('#loading-indicator').addClass('tw-hidden');
+ $('#rel-container').removeClass('tw-hidden');
+ $('#rev-container').removeClass('tw-hidden');
})();
};
const dropdownSelected = params.getAll('branch');
diff --git a/web_src/js/features/repo-home.js b/web_src/js/features/repo-home.js
index 6ac7b96b9e..e195c23c37 100644
--- a/web_src/js/features/repo-home.js
+++ b/web_src/js/features/repo-home.js
@@ -6,55 +6,57 @@ import {POST} from '../modules/fetch.js';
const {appSubUrl} = window.config;
export function initRepoTopicBar() {
- const $mgrBtn = $('#manage_topic');
- if (!$mgrBtn.length) return;
- const $editDiv = $('#topic_edit');
- const $viewDiv = $('#repo-topics');
- const $saveBtn = $('#save_topic');
- const $topicDropdown = $('#topic_edit .dropdown');
- const $topicForm = $editDiv; // the old logic, $editDiv is topicForm
+ const mgrBtn = document.getElementById('manage_topic');
+ if (!mgrBtn) return;
+ const editDiv = document.getElementById('topic_edit');
+ const viewDiv = document.getElementById('repo-topics');
+ const saveBtn = document.getElementById('save_topic');
+ const topicDropdown = editDiv.querySelector('.dropdown');
+ const $topicDropdown = $(topicDropdown);
+ const $topicForm = $(editDiv);
const $topicDropdownSearch = $topicDropdown.find('input.search');
const topicPrompts = {
- countPrompt: $topicDropdown.attr('data-text-count-prompt'),
- formatPrompt: $topicDropdown.attr('data-text-format-prompt'),
+ countPrompt: topicDropdown.getAttribute('data-text-count-prompt') ?? undefined,
+ formatPrompt: topicDropdown.getAttribute('data-text-format-prompt') ?? undefined,
};
- $mgrBtn.on('click', () => {
- hideElem($viewDiv);
- showElem($editDiv);
+ mgrBtn.addEventListener('click', () => {
+ hideElem(viewDiv);
+ showElem(editDiv);
$topicDropdownSearch.trigger('focus');
});
$('#cancel_topic_edit').on('click', () => {
- hideElem($editDiv);
- showElem($viewDiv);
- $mgrBtn.trigger('focus');
+ hideElem(editDiv);
+ showElem(viewDiv);
+ mgrBtn.focus();
});
- $saveBtn.on('click', async () => {
+ saveBtn.addEventListener('click', async () => {
const topics = $('input[name=topics]').val();
const data = new FormData();
data.append('topics', topics);
- const response = await POST($saveBtn.attr('data-link'), {data});
+ const response = await POST(saveBtn.getAttribute('data-link'), {data});
if (response.ok) {
const responseData = await response.json();
if (responseData.status === 'ok') {
- $viewDiv.children('.topic').remove();
+ $(viewDiv).children('.topic').remove();
if (topics.length) {
const topicArray = topics.split(',');
topicArray.sort();
for (const topic of topicArray) {
- const $link = $('<a class="ui repo-topic large label topic gt-m-0"></a>');
- $link.attr('href', `${appSubUrl}/explore/repos?q=${encodeURIComponent(topic)}&topic=1`);
- $link.text(topic);
- $link.insertBefore($mgrBtn); // insert all new topics before manage button
+ const link = document.createElement('a');
+ link.classList.add('ui', 'repo-topic', 'large', 'label', 'topic', 'tw-m-0');
+ link.href = `${appSubUrl}/explore/repos?q=${encodeURIComponent(topic)}&topic=1`;
+ link.textContent = topic;
+ mgrBtn.parentNode.insertBefore(link, mgrBtn); // insert all new topics before manage button
}
}
- hideElem($editDiv);
- showElem($viewDiv);
+ hideElem(editDiv);
+ showElem(viewDiv);
}
} else if (response.status === 422) {
const responseData = await response.json();
@@ -144,18 +146,18 @@ export function initRepoTopicBar() {
},
onAdd(addedValue, _addedText, $addedChoice) {
addedValue = addedValue.toLowerCase().trim();
- $($addedChoice).attr('data-value', addedValue);
- $($addedChoice).attr('data-text', addedValue);
+ $($addedChoice)[0].setAttribute('data-value', addedValue);
+ $($addedChoice)[0].setAttribute('data-text', addedValue);
},
});
$.fn.form.settings.rules.validateTopic = function (_values, regExp) {
const $topics = $topicDropdown.children('a.ui.label');
- const status = $topics.length === 0 || $topics.last().attr('data-value').match(regExp);
+ const status = !$topics.length || $topics.last()[0].getAttribute('data-value').match(regExp);
if (!status) {
$topics.last().removeClass('green').addClass('red');
}
- return status && $topicDropdown.children('a.ui.label.red').length === 0;
+ return status && !$topicDropdown.children('a.ui.label.red').length;
};
$topicForm.form({
diff --git a/web_src/js/features/repo-issue-content.js b/web_src/js/features/repo-issue-content.js
index e7768b066e..3c4efe0447 100644
--- a/web_src/js/features/repo-issue-content.js
+++ b/web_src/js/features/repo-issue-content.js
@@ -18,7 +18,7 @@ function showContentHistoryDetail(issueBaseUrl, commentId, historyId, itemTitleH
${svg('octicon-x', 16, 'close icon inside')}
<div class="header tw-flex tw-items-center tw-justify-between">
<div>${itemTitleHtml}</div>
- <div class="ui dropdown dialog-header-options gt-mr-5 gt-hidden">
+ <div class="ui dropdown dialog-header-options tw-mr-8 tw-hidden">
${i18nTextOptions}
${svg('octicon-triangle-down', 14, 'dropdown icon')}
<div class="menu">
@@ -76,7 +76,7 @@ function showContentHistoryDetail(issueBaseUrl, commentId, historyId, itemTitleH
$dialog.find('.comment-diff-data').removeClass('is-loading').html(resp.diffHtml);
// there is only one option "item[data-option-item=delete]", so the dropdown can be entirely shown/hidden.
if (resp.canSoftDelete) {
- $dialog.find('.dialog-header-options').removeClass('gt-hidden');
+ $dialog.find('.dialog-header-options').removeClass('tw-hidden');
}
} catch (error) {
console.error('Error:', error);
diff --git a/web_src/js/features/repo-issue-list.js b/web_src/js/features/repo-issue-list.js
index 4bdd5e5a8e..4582f87425 100644
--- a/web_src/js/features/repo-issue-list.js
+++ b/web_src/js/features/repo-issue-list.js
@@ -109,7 +109,7 @@ function initRepoIssueListAuthorDropdown() {
const processedResults = []; // to be used by dropdown to generate menu items
for (const item of resp.results) {
let html = `<img class="ui avatar tw-align-middle" src="${htmlEscape(item.avatar_link)}" aria-hidden="true" alt="" width="20" height="20"><span class="gt-ellipsis">${htmlEscape(item.username)}</span>`;
- if (item.full_name) html += `<span class="search-fullname gt-ml-3">${htmlEscape(item.full_name)}</span>`;
+ if (item.full_name) html += `<span class="search-fullname tw-ml-2">${htmlEscape(item.full_name)}</span>`;
processedResults.push({value: item.user_id, name: html});
}
resp.results = processedResults;
@@ -188,8 +188,6 @@ async function initIssuePinSort() {
createSortable(pinDiv, {
group: 'shared',
- animation: 150,
- ghostClass: 'card-ghost',
onEnd: pinMoveEnd,
});
}
diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js
index bf4ec15372..47b4d5f71c 100644
--- a/web_src/js/features/repo-issue.js
+++ b/web_src/js/features/repo-issue.js
@@ -42,23 +42,23 @@ export function initRepoIssueTimeTracking() {
}
async function updateDeadline(deadlineString) {
- hideElem($('#deadline-err-invalid-date'));
- $('#deadline-loader').addClass('is-loading');
+ hideElem('#deadline-err-invalid-date');
+ document.getElementById('deadline-loader')?.classList.add('is-loading');
let realDeadline = null;
if (deadlineString !== '') {
const newDate = Date.parse(deadlineString);
if (Number.isNaN(newDate)) {
- $('#deadline-loader').removeClass('is-loading');
- showElem($('#deadline-err-invalid-date'));
+ document.getElementById('deadline-loader')?.classList.remove('is-loading');
+ showElem('#deadline-err-invalid-date');
return false;
}
realDeadline = new Date(newDate);
}
try {
- const response = await POST($('#update-issue-deadline-form').attr('action'), {
+ const response = await POST(document.getElementById('update-issue-deadline-form').getAttribute('action'), {
data: {due_date: realDeadline},
});
@@ -69,8 +69,8 @@ async function updateDeadline(deadlineString) {
}
} catch (error) {
console.error(error);
- $('#deadline-loader').removeClass('is-loading');
- showElem($('#deadline-err-invalid-date'));
+ document.getElementById('deadline-loader').classList.remove('is-loading');
+ showElem('#deadline-err-invalid-date');
}
}
@@ -87,6 +87,19 @@ export function initRepoIssueDue() {
});
}
+/**
+ * @param {HTMLElement} item
+ */
+function excludeLabel(item) {
+ const href = item.getAttribute('href');
+ const id = item.getAttribute('data-label-id');
+
+ const regStr = `labels=((?:-?[0-9]+%2c)*)(${id})((?:%2c-?[0-9]+)*)&`;
+ const newStr = 'labels=$1-$2$3&';
+
+ window.location = href.replace(new RegExp(regStr), newStr);
+}
+
export function initRepoIssueSidebarList() {
const repolink = $('#repolink').val();
const repoId = $('#repoId').val();
@@ -123,16 +136,6 @@ export function initRepoIssueSidebarList() {
fullTextSearch: true,
});
- function excludeLabel(item) {
- const href = $(item).attr('href');
- const id = $(item).data('label-id');
-
- const regStr = `labels=((?:-?[0-9]+%2c)*)(${id})((?:%2c-?[0-9]+)*)&`;
- const newStr = 'labels=$1-$2$3&';
-
- window.location = href.replace(new RegExp(regStr), newStr);
- }
-
$('.menu a.label-filter-item').each(function () {
$(this).on('click', function (e) {
if (e.altKey) {
@@ -144,9 +147,9 @@ export function initRepoIssueSidebarList() {
$('.menu .ui.dropdown.label-filter').on('keydown', (e) => {
if (e.altKey && e.keyCode === 13) {
- const $selectedItems = $('.menu .ui.dropdown.label-filter .menu .item.selected');
- if ($selectedItems.length > 0) {
- excludeLabel($($selectedItems[0]));
+ const selectedItem = document.querySelector('.menu .ui.dropdown.label-filter .menu .item.selected');
+ if (selectedItem) {
+ excludeLabel(selectedItem);
}
}
});
@@ -166,11 +169,11 @@ export function initRepoIssueCommentDelete() {
const $parentTimelineGroup = $this.closest('.timeline-item-group');
// Check if this was a pending comment.
if ($conversationHolder.find('.pending-label').length) {
- const $counter = $('#review-box .review-comments-counter');
- let num = parseInt($counter.attr('data-pending-comment-number')) - 1 || 0;
+ const counter = document.querySelector('#review-box .review-comments-counter');
+ let num = parseInt(counter?.getAttribute('data-pending-comment-number')) - 1 || 0;
num = Math.max(num, 0);
- $counter.attr('data-pending-comment-number', num);
- $counter.text(num);
+ counter.setAttribute('data-pending-comment-number', num);
+ counter.textContent = String(num);
}
$(`#${$this.data('comment-id')}`).remove();
@@ -222,7 +225,7 @@ export function initRepoIssueCodeCommentCancel() {
$(document).on('click', '.cancel-code-comment', (e) => {
const $form = $(e.currentTarget).closest('form');
if ($form.length > 0 && $form.hasClass('comment-form')) {
- $form.addClass('gt-hidden');
+ $form.addClass('tw-hidden');
showElem($form.closest('.comment-code-cloud').find('button.comment-form-reply'));
} else {
$form.closest('.comment-code-cloud').remove();
@@ -279,14 +282,16 @@ export function initRepoPullRequestMergeInstruction() {
}
export function initRepoPullRequestAllowMaintainerEdit() {
- const $checkbox = $('#allow-edits-from-maintainers');
- if (!$checkbox.length) return;
+ const checkbox = document.getElementById('allow-edits-from-maintainers');
+ if (!checkbox) return;
+
+ const $checkbox = $(checkbox);
- const promptError = $checkbox.attr('data-prompt-error');
+ const promptError = checkbox.getAttribute('data-prompt-error');
$checkbox.checkbox({
'onChange': async () => {
const checked = $checkbox.checkbox('is checked');
- let url = $checkbox.attr('data-url');
+ let url = checkbox.getAttribute('data-url');
url += '/set_allow_maintainer_edit';
$checkbox.checkbox('set disabled');
try {
@@ -298,7 +303,7 @@ export function initRepoPullRequestAllowMaintainerEdit() {
}
} catch (error) {
console.error(error);
- showTemporaryTooltip($checkbox[0], promptError);
+ showTemporaryTooltip(checkbox, promptError);
} finally {
$checkbox.checkbox('set enabled');
}
@@ -325,7 +330,9 @@ export function initRepoIssueReferenceRepositorySearch() {
},
onChange(_value, _text, $choice) {
const $form = $choice.closest('form');
- $form.attr('action', `${appSubUrl}/${_text}/issues/new`);
+ if (!$form.length) return;
+
+ $form[0].setAttribute('action', `${appSubUrl}/${_text}/issues/new`);
},
fullTextSearch: true,
});
@@ -362,7 +369,7 @@ export async function updateIssuesMeta(url, action, issue_ids, id) {
}
export function initRepoIssueComments() {
- if ($('.repository.view.issue .timeline').length === 0) return;
+ if (!$('.repository.view.issue .timeline').length) return;
$('.re-request-review').on('click', async function (e) {
e.preventDefault();
@@ -375,17 +382,16 @@ export function initRepoIssueComments() {
window.location.reload();
});
- $(document).on('click', (event) => {
- const $urlTarget = $(':target');
- if ($urlTarget.length === 0) return;
+ document.addEventListener('click', (e) => {
+ const urlTarget = document.querySelector(':target');
+ if (!urlTarget) return;
- const urlTargetId = $urlTarget.attr('id');
+ const urlTargetId = urlTarget.id;
if (!urlTargetId) return;
- if (!/^(issue|pull)(comment)?-\d+$/.test(urlTargetId)) return;
- const $target = $(event.target);
+ if (!/^(issue|pull)(comment)?-\d+$/.test(urlTargetId)) return;
- if ($target.closest(`#${urlTargetId}`).length === 0) {
+ if (!e.target.closest(`#${urlTargetId}`)) {
const scrollPosition = $(window).scrollTop();
window.location.hash = '';
$(window).scrollTop(scrollPosition);
@@ -397,7 +403,7 @@ export function initRepoIssueComments() {
export async function handleReply($el) {
hideElem($el);
const $form = $el.closest('.comment-code-cloud').find('.comment-form');
- $form.removeClass('gt-hidden');
+ $form.removeClass('tw-hidden');
const $textarea = $form.find('textarea');
let editor = getComboMarkdownEditor($textarea);
@@ -419,30 +425,33 @@ export function initRepoPullRequestReview() {
if (window.history.scrollRestoration !== 'manual') {
window.history.scrollRestoration = 'manual';
}
- const $commentDiv = $(window.location.hash);
- if ($commentDiv) {
+ const commentDiv = document.querySelector(window.location.hash);
+ if (commentDiv) {
// get the name of the parent id
- const groupID = $commentDiv.closest('div[id^="code-comments-"]').attr('id');
+ const groupID = commentDiv.closest('div[id^="code-comments-"]')?.getAttribute('id');
if (groupID && groupID.startsWith('code-comments-')) {
const id = groupID.slice(14);
- const $ancestorDiffBox = $commentDiv.closest('.diff-file-box');
+ const ancestorDiffBox = commentDiv.closest('.diff-file-box');
// on pages like conversation, there is no diff header
- const $diffHeader = $ancestorDiffBox.find('.diff-file-header');
+ const diffHeader = ancestorDiffBox?.querySelector('.diff-file-header');
+
// offset is for scrolling
let offset = 30;
- if ($diffHeader[0]) {
- offset += $('.diff-detail-box').outerHeight() + $diffHeader.outerHeight();
+ if (diffHeader) {
+ offset += $('.diff-detail-box').outerHeight() + $(diffHeader).outerHeight();
}
- $(`#show-outdated-${id}`).addClass('gt-hidden');
- $(`#code-comments-${id}`).removeClass('gt-hidden');
- $(`#code-preview-${id}`).removeClass('gt-hidden');
- $(`#hide-outdated-${id}`).removeClass('gt-hidden');
+
+ document.getElementById(`show-outdated-${id}`).classList.add('tw-hidden');
+ document.getElementById(`code-comments-${id}`).classList.remove('tw-hidden');
+ document.getElementById(`code-preview-${id}`).classList.remove('tw-hidden');
+ document.getElementById(`hide-outdated-${id}`).classList.remove('tw-hidden');
// if the comment box is folded, expand it
- if ($ancestorDiffBox.attr('data-folded') && $ancestorDiffBox.attr('data-folded') === 'true') {
- setFileFolding($ancestorDiffBox[0], $ancestorDiffBox.find('.fold-file')[0], false);
+ if (ancestorDiffBox.getAttribute('data-folded') === 'true') {
+ setFileFolding(ancestorDiffBox, ancestorDiffBox.querySelector('.fold-file'), false);
}
+
window.scrollTo({
- top: $commentDiv.offset().top - offset,
+ top: $(commentDiv).offset().top - offset,
behavior: 'instant',
});
}
@@ -452,19 +461,19 @@ export function initRepoPullRequestReview() {
$(document).on('click', '.show-outdated', function (e) {
e.preventDefault();
const id = $(this).data('comment');
- $(this).addClass('gt-hidden');
- $(`#code-comments-${id}`).removeClass('gt-hidden');
- $(`#code-preview-${id}`).removeClass('gt-hidden');
- $(`#hide-outdated-${id}`).removeClass('gt-hidden');
+ $(this).addClass('tw-hidden');
+ $(`#code-comments-${id}`).removeClass('tw-hidden');
+ $(`#code-preview-${id}`).removeClass('tw-hidden');
+ $(`#hide-outdated-${id}`).removeClass('tw-hidden');
});
$(document).on('click', '.hide-outdated', function (e) {
e.preventDefault();
const id = $(this).data('comment');
- $(this).addClass('gt-hidden');
- $(`#code-comments-${id}`).addClass('gt-hidden');
- $(`#code-preview-${id}`).addClass('gt-hidden');
- $(`#show-outdated-${id}`).removeClass('gt-hidden');
+ $(this).addClass('tw-hidden');
+ $(`#code-comments-${id}`).addClass('tw-hidden');
+ $(`#code-preview-${id}`).addClass('tw-hidden');
+ $(`#show-outdated-${id}`).removeClass('tw-hidden');
});
$(document).on('click', 'button.comment-form-reply', async function (e) {
@@ -478,9 +487,7 @@ export function initRepoPullRequestReview() {
}
// The following part is only for diff views
- if ($('.repository.pull.diff').length === 0) {
- return;
- }
+ if (!$('.repository.pull.diff').length) return;
const $reviewBtn = $('.js-btn-review');
const $panel = $reviewBtn.parent().find('.review-box-panel');
@@ -529,9 +536,9 @@ export function initRepoPullRequestReview() {
const $td = $ntr.find(`.add-comment-${side}`);
const $commentCloud = $td.find('.comment-code-cloud');
- if ($commentCloud.length === 0 && !$ntr.find('button[name="pending_review"]').length) {
+ if (!$commentCloud.length && !$ntr.find('button[name="pending_review"]').length) {
try {
- const response = await GET($(this).closest('[data-new-comment-url]').attr('data-new-comment-url'));
+ const response = await GET(this.closest('[data-new-comment-url]')?.getAttribute('data-new-comment-url'));
const html = await response.text();
$td.html(html);
$td.find("input[name='line']").val(idx);
@@ -587,6 +594,22 @@ export function initRepoIssueWipToggle() {
});
}
+async function pullrequest_targetbranch_change(update_url) {
+ const targetBranch = $('#pull-target-branch').data('branch');
+ const $branchTarget = $('#branch_target');
+ if (targetBranch === $branchTarget.text()) {
+ window.location.reload();
+ return false;
+ }
+ try {
+ await POST(update_url, {data: new URLSearchParams({target_branch: targetBranch})});
+ } catch (error) {
+ console.error(error);
+ } finally {
+ window.location.reload();
+ }
+}
+
export function initRepoIssueTitleEdit() {
// Edit issue title
const $issueTitle = $('#issue-title');
@@ -609,31 +632,15 @@ export function initRepoIssueTitleEdit() {
$('#edit-title').on('click', editTitleToggle);
$('#cancel-edit-title').on('click', editTitleToggle);
$('#save-edit-title').on('click', editTitleToggle).on('click', async function () {
- const pullrequest_targetbranch_change = async function (update_url) {
- const targetBranch = $('#pull-target-branch').data('branch');
- const $branchTarget = $('#branch_target');
- if (targetBranch === $branchTarget.text()) {
- window.location.reload();
- return false;
- }
- try {
- await POST(update_url, {data: new URLSearchParams({target_branch: targetBranch})});
- } catch (error) {
- console.error(error);
- } finally {
- window.location.reload();
- }
- };
-
- const pullrequest_target_update_url = $(this).attr('data-target-update-url');
- if ($editInput.val().length === 0 || $editInput.val() === $issueTitle.text()) {
+ const pullrequest_target_update_url = this.getAttribute('data-target-update-url');
+ if (!$editInput.val().length || $editInput.val() === $issueTitle.text()) {
$editInput.val($issueTitle.text());
await pullrequest_targetbranch_change(pullrequest_target_update_url);
} else {
try {
const params = new URLSearchParams();
params.append('title', $editInput.val());
- const response = await POST($(this).attr('data-update-url'), {data: params});
+ const response = await POST(this.getAttribute('data-update-url'), {data: params});
const data = await response.json();
$editInput.val(data.title);
$issueTitle.text(data.title);
@@ -673,10 +680,11 @@ export function initSingleCommentEditor($commentForm) {
// * normal new issue/pr page, no status-button
// * issue/pr view page, with comment form, has status-button
const opts = {};
- const $statusButton = $('#status-button');
- if ($statusButton.length) {
+ const statusButton = document.getElementById('status-button');
+ if (statusButton) {
opts.onContentChanged = (editor) => {
- $statusButton.text($statusButton.attr(editor.value().trim() ? 'data-status-and-comment' : 'data-status'));
+ const statusText = statusButton.getAttribute(editor.value().trim() ? 'data-status-and-comment' : 'data-status');
+ statusButton.textContent = statusText;
};
}
initComboMarkdownEditor($commentForm.find('.combo-markdown-editor'), opts);
diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js
index 33d8ab2918..2f5514a289 100644
--- a/web_src/js/features/repo-legacy.js
+++ b/web_src/js/features/repo-legacy.js
@@ -31,7 +31,7 @@ const {csrfToken} = window.config;
// if there are draft comments, confirm before reloading, to avoid losing comments
function reloadConfirmDraftComment() {
const commentTextareas = [
- document.querySelector('.edit-content-zone:not(.gt-hidden) textarea'),
+ document.querySelector('.edit-content-zone:not(.tw-hidden) textarea'),
document.querySelector('#comment-form textarea'),
];
for (const textarea of commentTextareas) {
@@ -50,9 +50,7 @@ function reloadConfirmDraftComment() {
export function initRepoCommentForm() {
const $commentForm = $('.comment.form');
- if ($commentForm.length === 0) {
- return;
- }
+ if (!$commentForm.length) return;
if ($commentForm.find('.field.combo-editor-dropzone').length) {
// at the moment, if a form has multiple combo-markdown-editors, it must be an issue template form
@@ -76,11 +74,11 @@ export function initRepoCommentForm() {
}
if (editMode === 'true') {
- const $form = $('#update_issueref_form');
+ const form = document.getElementById('update_issueref_form');
const params = new URLSearchParams();
params.append('ref', selectedValue);
try {
- await POST($form.attr('action'), {data: params});
+ await POST(form.getAttribute('action'), {data: params});
window.location.reload();
} catch (error) {
console.error(error);
@@ -139,19 +137,19 @@ export function initRepoCommentForm() {
hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
- const $clickedItem = $(this);
- const scope = $(this).attr('data-scope');
+ const clickedItem = this; // eslint-disable-line unicorn/no-this-assignment
+ const scope = this.getAttribute('data-scope');
$(this).parent().find('.item').each(function () {
if (scope) {
// Enable only clicked item for scoped labels
- if ($(this).attr('data-scope') !== scope) {
+ if (this.getAttribute('data-scope') !== scope) {
return true;
}
- if (!$(this).is($clickedItem) && !$(this).hasClass('checked')) {
+ if (this !== clickedItem && !$(this).hasClass('checked')) {
return true;
}
- } else if (!$(this).is($clickedItem)) {
+ } else if (this !== clickedItem) {
// Toggle for other labels
return true;
}
@@ -197,15 +195,15 @@ export function initRepoCommentForm() {
$(this).parent().find('.item').each(function () {
if ($(this).hasClass('checked')) {
listIds.push($(this).data('id'));
- $($(this).data('id-selector')).removeClass('gt-hidden');
+ $($(this).data('id-selector')).removeClass('tw-hidden');
} else {
- $($(this).data('id-selector')).addClass('gt-hidden');
+ $($(this).data('id-selector')).addClass('tw-hidden');
}
});
- if (listIds.length === 0) {
- $noSelect.removeClass('gt-hidden');
+ if (!listIds.length) {
+ $noSelect.removeClass('tw-hidden');
} else {
- $noSelect.addClass('gt-hidden');
+ $noSelect.addClass('tw-hidden');
}
$($(this).parent().data('id')).val(listIds.join(','));
return false;
@@ -234,9 +232,9 @@ export function initRepoCommentForm() {
}
$list.find('.item').each(function () {
- $(this).addClass('gt-hidden');
+ $(this).addClass('tw-hidden');
});
- $noSelect.removeClass('gt-hidden');
+ $noSelect.removeClass('tw-hidden');
$($(this).parent().data('id')).val('');
});
}
@@ -272,11 +270,11 @@ export function initRepoCommentForm() {
let icon = '';
if (input_id === '#milestone_id') {
- icon = svg('octicon-milestone', 18, 'gt-mr-3');
+ icon = svg('octicon-milestone', 18, 'tw-mr-2');
} else if (input_id === '#project_id') {
- icon = svg('octicon-project', 18, 'gt-mr-3');
+ icon = svg('octicon-project', 18, 'tw-mr-2');
} else if (input_id === '#assignee_id') {
- icon = `<img class="ui avatar image gt-mr-3" alt="avatar" src=${$(this).data('avatar')}>`;
+ icon = `<img class="ui avatar image tw-mr-2" alt="avatar" src=${$(this).data('avatar')}>`;
}
$list.find('.selected').html(`
@@ -286,7 +284,7 @@ export function initRepoCommentForm() {
</a>
`);
- $(`.ui${select_id}.list .no-select`).addClass('gt-hidden');
+ $(`.ui${select_id}.list .no-select`).addClass('tw-hidden');
$(input_id).val($(this).data('id'));
});
$menu.find('.no-select.item').on('click', function () {
@@ -307,7 +305,7 @@ export function initRepoCommentForm() {
}
$list.find('.selected').html('');
- $list.find('.no-select').removeClass('gt-hidden');
+ $list.find('.no-select').removeClass('tw-hidden');
$(input_id).val('');
});
}
@@ -321,29 +319,32 @@ export function initRepoCommentForm() {
async function onEditContent(event) {
event.preventDefault();
- const $segment = $(this).closest('.header').next();
- const $editContentZone = $segment.find('.edit-content-zone');
- const $renderContent = $segment.find('.render-content');
- const $rawContent = $segment.find('.raw-content');
+ const segment = this.closest('.header').nextElementSibling;
+ const editContentZone = segment.querySelector('.edit-content-zone');
+ const renderContent = segment.querySelector('.render-content');
+ const rawContent = segment.querySelector('.raw-content');
let comboMarkdownEditor;
- const setupDropzone = async ($dropzone) => {
- if ($dropzone.length === 0) return null;
+ /**
+ * @param {HTMLElement} dropzone
+ */
+ const setupDropzone = async (dropzone) => {
+ if (!dropzone) return null;
let disableRemovedfileEvent = false; // when resetting the dropzone (removeAllFiles), disable the "removedfile" event
let fileUuidDict = {}; // to record: if a comment has been saved, then the uploaded files won't be deleted from server when clicking the Remove in the dropzone
- const dz = await createDropzone($dropzone[0], {
- url: $dropzone.attr('data-upload-url'),
+ const dz = await createDropzone(dropzone, {
+ url: dropzone.getAttribute('data-upload-url'),
headers: {'X-Csrf-Token': csrfToken},
- maxFiles: $dropzone.attr('data-max-file'),
- maxFilesize: $dropzone.attr('data-max-size'),
- acceptedFiles: (['*/*', ''].includes($dropzone.attr('data-accepts'))) ? null : $dropzone.attr('data-accepts'),
+ maxFiles: dropzone.getAttribute('data-max-file'),
+ maxFilesize: dropzone.getAttribute('data-max-size'),
+ acceptedFiles: ['*/*', ''].includes(dropzone.getAttribute('data-accepts')) ? null : dropzone.getAttribute('data-accepts'),
addRemoveLinks: true,
- dictDefaultMessage: $dropzone.attr('data-default-message'),
- dictInvalidFileType: $dropzone.attr('data-invalid-input-type'),
- dictFileTooBig: $dropzone.attr('data-file-too-big'),
- dictRemoveFile: $dropzone.attr('data-remove-file'),
+ dictDefaultMessage: dropzone.getAttribute('data-default-message'),
+ dictInvalidFileType: dropzone.getAttribute('data-invalid-input-type'),
+ dictFileTooBig: dropzone.getAttribute('data-file-too-big'),
+ dictRemoveFile: dropzone.getAttribute('data-remove-file'),
timeout: 0,
thumbnailMethod: 'contain',
thumbnailWidth: 480,
@@ -352,46 +353,54 @@ async function onEditContent(event) {
this.on('success', (file, data) => {
file.uuid = data.uuid;
fileUuidDict[file.uuid] = {submitted: false};
- const $input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
- $dropzone.find('.files').append($input);
+ const input = document.createElement('input');
+ input.id = data.uuid;
+ input.name = 'files';
+ input.type = 'hidden';
+ input.value = data.uuid;
+ dropzone.querySelector('.files').insertAdjacentHTML('beforeend', input.outerHTML);
});
this.on('removedfile', async (file) => {
if (disableRemovedfileEvent) return;
- $(`#${file.uuid}`).remove();
- if ($dropzone.attr('data-remove-url') && !fileUuidDict[file.uuid].submitted) {
+ document.getElementById(file.uuid)?.remove();
+ if (dropzone.getAttribute('data-remove-url') && !fileUuidDict[file.uuid].submitted) {
try {
- await POST($dropzone.attr('data-remove-url'), {data: new URLSearchParams({file: file.uuid})});
+ await POST(dropzone.getAttribute('data-remove-url'), {data: new URLSearchParams({file: file.uuid})});
} catch (error) {
console.error(error);
}
}
});
this.on('submit', () => {
- $.each(fileUuidDict, (fileUuid) => {
+ for (const fileUuid of Object.keys(fileUuidDict)) {
fileUuidDict[fileUuid].submitted = true;
- });
+ }
});
this.on('reload', async () => {
try {
- const response = await GET($editContentZone.attr('data-attachment-url'));
+ const response = await GET(editContentZone.getAttribute('data-attachment-url'));
const data = await response.json();
// do not trigger the "removedfile" event, otherwise the attachments would be deleted from server
disableRemovedfileEvent = true;
dz.removeAllFiles(true);
- $dropzone.find('.files').empty();
+ dropzone.querySelector('.files').innerHTML = '';
fileUuidDict = {};
disableRemovedfileEvent = false;
for (const attachment of data) {
- const imgSrc = `${$dropzone.attr('data-link-url')}/${attachment.uuid}`;
+ const imgSrc = `${dropzone.getAttribute('data-link-url')}/${attachment.uuid}`;
dz.emit('addedfile', attachment);
dz.emit('thumbnail', attachment, imgSrc);
dz.emit('complete', attachment);
dz.files.push(attachment);
fileUuidDict[attachment.uuid] = {submitted: true};
- $dropzone.find(`img[src='${imgSrc}']`)[0].style.maxWidth = '100%';
- const $input = $(`<input id="${attachment.uuid}" name="files" type="hidden">`).val(attachment.uuid);
- $dropzone.find('.files').append($input);
+ dropzone.querySelector(`img[src='${imgSrc}']`).style.maxWidth = '100%';
+ const input = document.createElement('input');
+ input.id = attachment.uuid;
+ input.name = 'files';
+ input.type = 'hidden';
+ input.value = attachment.uuid;
+ dropzone.querySelector('.files').insertAdjacentHTML('beforeend', input.outerHTML);
}
} catch (error) {
console.error(error);
@@ -404,44 +413,44 @@ async function onEditContent(event) {
};
const cancelAndReset = (dz) => {
- showElem($renderContent);
- hideElem($editContentZone);
+ showElem(renderContent);
+ hideElem(editContentZone);
if (dz) {
dz.emit('reload');
}
};
const saveAndRefresh = async (dz) => {
- showElem($renderContent);
- hideElem($editContentZone);
+ showElem(renderContent);
+ hideElem(editContentZone);
try {
const params = new URLSearchParams({
content: comboMarkdownEditor.value(),
- context: $editContentZone.attr('data-context'),
+ context: editContentZone.getAttribute('data-context'),
});
for (const file of dz.files) params.append('files[]', file.uuid);
- const response = await POST($editContentZone.attr('data-update-url'), {data: params});
+ const response = await POST(editContentZone.getAttribute('data-update-url'), {data: params});
const data = await response.json();
if (!data.content) {
- $renderContent.html($('#no-content').html());
- $rawContent.text('');
+ renderContent.innerHTML = document.getElementById('no-content').innerHTML;
+ rawContent.textContent = '';
} else {
- $renderContent.html(data.content);
- $rawContent.text(comboMarkdownEditor.value());
- const $refIssues = $renderContent.find('p .ref-issue');
- attachRefIssueContextPopup($refIssues);
+ renderContent.innerHTML = data.content;
+ rawContent.textContent = comboMarkdownEditor.value();
+ const refIssues = renderContent.querySelectorAll('p .ref-issue');
+ attachRefIssueContextPopup(refIssues);
}
- const $content = $segment;
- if (!$content.find('.dropzone-attachments').length) {
+ const content = segment;
+ if (!content.querySelector('.dropzone-attachments')) {
if (data.attachments !== '') {
- $content[0].insertAdjacentHTML('beforeend', data.attachments);
+ content.insertAdjacentHTML('beforeend', data.attachments);
}
} else if (data.attachments === '') {
- $content.find('.dropzone-attachments').remove();
+ content.querySelector('.dropzone-attachments').remove();
} else {
- $content.find('.dropzone-attachments')[0].outerHTML = data.attachments;
+ content.querySelector('.dropzone-attachments').outerHTML = data.attachments;
}
if (dz) {
dz.emit('submit');
@@ -454,40 +463,38 @@ async function onEditContent(event) {
}
};
- if (!$editContentZone.html()) {
- $editContentZone.html($('#issue-comment-editor-template').html());
- comboMarkdownEditor = await initComboMarkdownEditor($editContentZone.find('.combo-markdown-editor'));
+ if (!editContentZone.innerHTML) {
+ editContentZone.innerHTML = document.getElementById('issue-comment-editor-template').innerHTML;
+ comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
- const $dropzone = $editContentZone.find('.dropzone');
- const dz = await setupDropzone($dropzone);
- $editContentZone.find('.cancel.button').on('click', (e) => {
+ const dropzone = editContentZone.querySelector('.dropzone');
+ const dz = await setupDropzone(dropzone);
+ editContentZone.querySelector('.cancel.button').addEventListener('click', (e) => {
e.preventDefault();
cancelAndReset(dz);
});
- $editContentZone.find('.save.button').on('click', (e) => {
+ editContentZone.querySelector('.save.button').addEventListener('click', (e) => {
e.preventDefault();
saveAndRefresh(dz);
});
- $editContentZone.on('ce-quick-submit', () => {
- $editContentZone.find('.save.button').trigger('click');
+ editContentZone.on('ce-quick-submit', () => {
+ editContentZone.find('.save.button').trigger('click');
});
} else {
- comboMarkdownEditor = getComboMarkdownEditor($editContentZone.find('.combo-markdown-editor'));
+ comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
}
// Show write/preview tab and copy raw content as needed
- showElem($editContentZone);
- hideElem($renderContent);
+ showElem(editContentZone);
+ hideElem(renderContent);
if (!comboMarkdownEditor.value()) {
- comboMarkdownEditor.value($rawContent.text());
+ comboMarkdownEditor.value(rawContent.textContent);
}
comboMarkdownEditor.focus();
}
export function initRepository() {
- if ($('.page-content.repository').length === 0) {
- return;
- }
+ if (!$('.page-content.repository').length) return;
initRepoBranchTagSelector('.js-branch-tag-selector');
diff --git a/web_src/js/features/repo-projects.js b/web_src/js/features/repo-projects.js
index fc688bb695..d9ae85a8d2 100644
--- a/web_src/js/features/repo-projects.js
+++ b/web_src/js/features/repo-projects.js
@@ -58,9 +58,7 @@ async function initRepoProjectSortable() {
createSortable(mainBoard, {
group: 'project-column',
draggable: '.project-column',
- filter: '[data-id="0"]',
- animation: 150,
- ghostClass: 'card-ghost',
+ handle: '.project-column-header',
delayOnTouchOnly: true,
delay: 500,
onSort: async () => {
@@ -87,8 +85,6 @@ async function initRepoProjectSortable() {
const boardCardList = boardColumn.getElementsByClassName('cards')[0];
createSortable(boardCardList, {
group: 'shared',
- animation: 150,
- ghostClass: 'card-ghost',
onAdd: moveIssue,
onUpdate: moveIssue,
delayOnTouchOnly: true,
diff --git a/web_src/js/features/repo-settings.js b/web_src/js/features/repo-settings.js
index dc1db8ab29..0ea44130d0 100644
--- a/web_src/js/features/repo-settings.js
+++ b/web_src/js/features/repo-settings.js
@@ -71,7 +71,7 @@ export function initRepoSettingSearchTeamBox() {
}
export function initRepoSettingGitHook() {
- if ($('.edit.githook').length === 0) return;
+ if (!$('.edit.githook').length) return;
const filename = document.querySelector('.hook-filename').textContent;
const _promise = createMonaco($('#content')[0], filename, {language: 'shell'});
}
diff --git a/web_src/js/features/repo-unicode-escape.js b/web_src/js/features/repo-unicode-escape.js
index d878532001..9f0c745223 100644
--- a/web_src/js/features/repo-unicode-escape.js
+++ b/web_src/js/features/repo-unicode-escape.js
@@ -7,8 +7,8 @@ export function initUnicodeEscapeButton() {
e.preventDefault();
- const fileContent = btn.closest('.file-content, .non-diff-file-content');
- const fileView = fileContent?.querySelectorAll('.file-code, .file-view');
+ const fileContent = btn.closest('.file-content, .non-diff-file-content, .file-preview-box');
+ const fileView = fileContent?.querySelectorAll('.file-code, .file-view, .file-preview');
if (btn.matches('.escape-button')) {
for (const el of fileView) el.classList.add('unicode-escaped');
hideElem(btn);
diff --git a/web_src/js/features/tribute.js b/web_src/js/features/tribute.js
index 70a5de6913..02cd484374 100644
--- a/web_src/js/features/tribute.js
+++ b/web_src/js/features/tribute.js
@@ -36,7 +36,7 @@ function makeCollections({mentions, emoji}) {
menuItemTemplate: (item) => {
return `
<div class="tribute-item">
- <img src="${htmlEscape(item.original.avatar)}" class="gt-mr-3"/>
+ <img src="${htmlEscape(item.original.avatar)}" class="tw-mr-2"/>
<span class="name">${htmlEscape(item.original.name)}</span>
${item.original.fullname && item.original.fullname !== '' ? `<span class="fullname">${htmlEscape(item.original.fullname)}</span>` : ''}
</div>
diff --git a/web_src/js/features/user-settings.js b/web_src/js/features/user-settings.js
index 0dd908f34a..2d8c53e457 100644
--- a/web_src/js/features/user-settings.js
+++ b/web_src/js/features/user-settings.js
@@ -1,7 +1,7 @@
import {hideElem, showElem} from '../utils/dom.js';
export function initUserSettings() {
- if (document.querySelectorAll('.user.settings.profile').length === 0) return;
+ if (!document.querySelectorAll('.user.settings.profile').length) return;
const usernameInput = document.getElementById('username');
if (!usernameInput) return;
diff --git a/web_src/js/index.js b/web_src/js/index.js
index abf0d469d1..4c707486bd 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -13,6 +13,7 @@ import {initImageDiff} from './features/imagediff.js';
import {initRepoMigration} from './features/repo-migration.js';
import {initRepoProject} from './features/repo-projects.js';
import {initTableSort} from './features/tablesort.js';
+import {initAutoFocusEnd} from './features/autofocus-end.js';
import {initAdminUserListSearchForm} from './features/admin/users.js';
import {initAdminConfigs} from './features/admin/config.js';
import {initMarkupAnchors} from './markup/anchors.js';
@@ -122,6 +123,7 @@ onDomReady(() => {
initSshKeyFormParser();
initStopwatch();
initTableSort();
+ initAutoFocusEnd();
initFindFileInRepo();
initCopyContent();
diff --git a/web_src/js/markup/mermaid.js b/web_src/js/markup/mermaid.js
index 82e9909fec..0549fb3e31 100644
--- a/web_src/js/markup/mermaid.js
+++ b/web_src/js/markup/mermaid.js
@@ -49,7 +49,7 @@ export async function renderMermaid() {
iframe.srcdoc = `<html><head><style>${iframeCss}</style></head><body>${svg}</body></html>`;
const mermaidBlock = document.createElement('div');
- mermaidBlock.classList.add('mermaid-block', 'is-loading', 'gt-hidden');
+ mermaidBlock.classList.add('mermaid-block', 'is-loading', 'tw-hidden');
mermaidBlock.append(iframe);
const btn = makeCodeCopyButton();
@@ -58,7 +58,7 @@ export async function renderMermaid() {
iframe.addEventListener('load', () => {
pre.replaceWith(mermaidBlock);
- mermaidBlock.classList.remove('gt-hidden');
+ mermaidBlock.classList.remove('tw-hidden');
iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`;
setTimeout(() => { // avoid flash of iframe background
mermaidBlock.classList.remove('is-loading');
diff --git a/web_src/js/modules/fomantic/dropdown.js b/web_src/js/modules/fomantic/dropdown.js
index 7302078dbd..e795e8e2c8 100644
--- a/web_src/js/modules/fomantic/dropdown.js
+++ b/web_src/js/modules/fomantic/dropdown.js
@@ -21,12 +21,11 @@ function ariaDropdownFn(...args) {
// it means that this call will reset the dropdown internal settings, then we need to re-delegate the callbacks.
const needDelegate = (!args.length || typeof args[0] !== 'string');
for (const el of this) {
- const $dropdown = $(el);
if (!el[ariaPatchKey]) {
- attachInit($dropdown);
+ attachInit(el);
}
if (needDelegate) {
- delegateOne($dropdown);
+ delegateOne($(el));
}
}
return ret;
@@ -40,17 +39,23 @@ function updateMenuItem(dropdown, item) {
item.setAttribute('tabindex', '-1');
for (const el of item.querySelectorAll('a, input, button')) el.setAttribute('tabindex', '-1');
}
-
-// make the label item and its "delete icon" has correct aria attributes
-function updateSelectionLabel($label) {
+/**
+ * make the label item and its "delete icon" have correct aria attributes
+ * @param {HTMLElement} label
+ */
+function updateSelectionLabel(label) {
// the "label" is like this: "<a|div class="ui label" data-value="1">the-label-name <i|svg class="delete icon"/></a>"
- if (!$label.attr('id')) $label.attr('id', generateAriaId());
- $label.attr('tabindex', '-1');
- $label.find('.delete.icon').attr({
- 'aria-hidden': 'false',
- 'aria-label': window.config.i18n.remove_label_str.replace('%s', $label.attr('data-value')),
- 'role': 'button',
- });
+ if (!label.id) {
+ label.id = generateAriaId();
+ }
+ label.tabIndex = -1;
+
+ const deleteIcon = label.querySelector('.delete.icon');
+ if (deleteIcon) {
+ deleteIcon.setAttribute('aria-hidden', 'false');
+ deleteIcon.setAttribute('aria-label', window.config.i18n.remove_label_str.replace('%s', label.getAttribute('data-value')));
+ deleteIcon.setAttribute('role', 'button');
+ }
}
// delegate the dropdown's template functions and callback functions to add aria attributes.
@@ -86,43 +91,44 @@ function delegateOne($dropdown) {
const dropdownOnLabelCreateOld = dropdownCall('setting', 'onLabelCreate');
dropdownCall('setting', 'onLabelCreate', function(value, text) {
const $label = dropdownOnLabelCreateOld.call(this, value, text);
- updateSelectionLabel($label);
+ updateSelectionLabel($label[0]);
return $label;
});
}
// for static dropdown elements (generated by server-side template), prepare them with necessary aria attributes
-function attachStaticElements($dropdown, $focusable, $menu) {
- const dropdown = $dropdown[0];
-
+function attachStaticElements(dropdown, focusable, menu) {
// prepare static dropdown menu list popup
- if (!$menu.attr('id')) $menu.attr('id', generateAriaId());
- $menu.find('> .item').each((_, item) => updateMenuItem(dropdown, item));
+ if (!menu.id) {
+ menu.id = generateAriaId();
+ }
+
+ $(menu).find('> .item').each((_, item) => updateMenuItem(dropdown, item));
+
// this role could only be changed after its content is ready, otherwise some browsers+readers (like Chrome+AppleVoice) crash
- $menu.attr('role', dropdown[ariaPatchKey].listPopupRole);
+ menu.setAttribute('role', dropdown[ariaPatchKey].listPopupRole);
// prepare selection label items
- $dropdown.find('.ui.label').each((_, label) => updateSelectionLabel($(label)));
+ for (const label of dropdown.querySelectorAll('.ui.label')) {
+ updateSelectionLabel(label);
+ }
// make the primary element (focusable) aria-friendly
- $focusable.attr({
- 'role': $focusable.attr('role') ?? dropdown[ariaPatchKey].focusableRole,
- 'aria-haspopup': dropdown[ariaPatchKey].listPopupRole,
- 'aria-controls': $menu.attr('id'),
- 'aria-expanded': 'false',
- });
+ focusable.setAttribute('role', focusable.getAttribute('role') ?? dropdown[ariaPatchKey].focusableRole);
+ focusable.setAttribute('aria-haspopup', dropdown[ariaPatchKey].listPopupRole);
+ focusable.setAttribute('aria-controls', menu.id);
+ focusable.setAttribute('aria-expanded', 'false');
// use tooltip's content as aria-label if there is no aria-label
- const tooltipContent = $dropdown.attr('data-tooltip-content');
- if (tooltipContent && !$dropdown.attr('aria-label')) {
- $dropdown.attr('aria-label', tooltipContent);
+ const tooltipContent = dropdown.getAttribute('data-tooltip-content');
+ if (tooltipContent && !dropdown.getAttribute('aria-label')) {
+ dropdown.setAttribute('aria-label', tooltipContent);
}
}
-function attachInit($dropdown) {
- const dropdown = $dropdown[0];
+function attachInit(dropdown) {
dropdown[ariaPatchKey] = {};
- if ($dropdown.hasClass('custom')) return;
+ if (dropdown.classList.contains('custom')) return;
// Dropdown has 2 different focusing behaviors
// * with search input: the input is focused, and it works with aria-activedescendant pointing another sibling element.
@@ -139,67 +145,69 @@ function attachInit($dropdown) {
// TODO: multiple selection is only partially supported. Check and test them one by one in the future.
- const $textSearch = $dropdown.find('input.search').eq(0);
- const $focusable = $textSearch.length ? $textSearch : $dropdown; // the primary element for focus, see comment above
- if (!$focusable.length) return;
+ const textSearch = dropdown.querySelector('input.search');
+ const focusable = textSearch || dropdown; // the primary element for focus, see comment above
+ if (!focusable) return;
// as a combobox, the input should not have autocomplete by default
- if ($textSearch.length && !$textSearch.attr('autocomplete')) {
- $textSearch.attr('autocomplete', 'off');
+ if (textSearch && !textSearch.getAttribute('autocomplete')) {
+ textSearch.setAttribute('autocomplete', 'off');
}
- let $menu = $dropdown.find('> .menu');
- if (!$menu.length) {
+ let menu = $(dropdown).find('> .menu')[0];
+ if (!menu) {
// some "multiple selection" dropdowns don't have a static menu element in HTML, we need to pre-create it to make it have correct aria attributes
- $menu = $('<div class="menu"></div>').appendTo($dropdown);
+ menu = document.createElement('div');
+ menu.classList.add('menu');
+ dropdown.append(menu);
}
// There are 2 possible solutions about the role: combobox or menu.
// The idea is that if there is an input, then it's a combobox, otherwise it's a menu.
// Since #19861 we have prepared the "combobox" solution, but didn't get enough time to put it into practice and test before.
- const isComboBox = $dropdown.find('input').length > 0;
+ const isComboBox = dropdown.querySelectorAll('input').length > 0;
dropdown[ariaPatchKey].focusableRole = isComboBox ? 'combobox' : 'menu';
dropdown[ariaPatchKey].listPopupRole = isComboBox ? 'listbox' : '';
dropdown[ariaPatchKey].listItemRole = isComboBox ? 'option' : 'menuitem';
- attachDomEvents($dropdown, $focusable, $menu);
- attachStaticElements($dropdown, $focusable, $menu);
+ attachDomEvents(dropdown, focusable, menu);
+ attachStaticElements(dropdown, focusable, menu);
}
-function attachDomEvents($dropdown, $focusable, $menu) {
- const dropdown = $dropdown[0];
+function attachDomEvents(dropdown, focusable, menu) {
// when showing, it has class: ".animating.in"
// when hiding, it has class: ".visible.animating.out"
- const isMenuVisible = () => ($menu.hasClass('visible') && !$menu.hasClass('out')) || $menu.hasClass('in');
+ const isMenuVisible = () => (menu.classList.contains('visible') && !menu.classList.contains('out')) || menu.classList.contains('in');
// update aria attributes according to current active/selected item
const refreshAriaActiveItem = () => {
const menuVisible = isMenuVisible();
- $focusable.attr('aria-expanded', menuVisible ? 'true' : 'false');
+ focusable.setAttribute('aria-expanded', menuVisible ? 'true' : 'false');
// if there is an active item, use it (the user is navigating between items)
// otherwise use the "selected" for combobox (for the last selected item)
- const $active = $menu.find('> .item.active, > .item.selected');
+ const active = $(menu).find('> .item.active, > .item.selected')[0];
+ if (!active) return;
// if the popup is visible and has an active/selected item, use its id as aria-activedescendant
if (menuVisible) {
- $focusable.attr('aria-activedescendant', $active.attr('id'));
+ focusable.setAttribute('aria-activedescendant', active.id);
} else if (dropdown[ariaPatchKey].listPopupRole === 'menu') {
// for menu, when the popup is hidden, no need to keep the aria-activedescendant, and clear the active/selected item
- $focusable.removeAttr('aria-activedescendant');
- $active.removeClass('active').removeClass('selected');
+ focusable.removeAttribute('aria-activedescendant');
+ active.classList.remove('active', 'selected');
}
};
- $dropdown.on('keydown', (e) => {
+ dropdown.addEventListener('keydown', (e) => {
// here it must use keydown event before dropdown's keyup handler, otherwise there is no Enter event in our keyup handler
if (e.key === 'Enter') {
- const dropdownCall = fomanticDropdownFn.bind($dropdown);
+ const dropdownCall = fomanticDropdownFn.bind($(dropdown));
let $item = dropdownCall('get item', dropdownCall('get value'));
- if (!$item) $item = $menu.find('> .item.selected'); // when dropdown filters items by input, there is no "value", so query the "selected" item
+ if (!$item) $item = $(menu).find('> .item.selected'); // when dropdown filters items by input, there is no "value", so query the "selected" item
// if the selected item is clickable, then trigger the click event.
// we can not click any item without check, because Fomantic code might also handle the Enter event. that would result in double click.
- if ($item && ($item.is('a') || $item.hasClass('js-aria-clickable'))) $item[0].click();
+ if ($item && ($item[0].matches('a') || $item.hasClass('js-aria-clickable'))) $item[0].click();
}
});
@@ -209,7 +217,7 @@ function attachDomEvents($dropdown, $focusable, $menu) {
// without the delay for hiding, the UI will be somewhat laggy and sometimes may get stuck in the animation.
const deferredRefreshAriaActiveItem = (delay = 0) => { setTimeout(refreshAriaActiveItem, delay) };
dropdown[ariaPatchKey].deferredRefreshAriaActiveItem = deferredRefreshAriaActiveItem;
- $dropdown.on('keyup', (e) => { if (e.key.startsWith('Arrow')) deferredRefreshAriaActiveItem(); });
+ dropdown.addEventListener('keyup', (e) => { if (e.key.startsWith('Arrow')) deferredRefreshAriaActiveItem(); });
// if the dropdown has been opened by focus, do not trigger the next click event again.
// otherwise the dropdown will be closed immediately, especially on Android with TalkBack
diff --git a/web_src/js/modules/fomantic/modal.js b/web_src/js/modules/fomantic/modal.js
index 7c9aade790..8b455cf4de 100644
--- a/web_src/js/modules/fomantic/modal.js
+++ b/web_src/js/modules/fomantic/modal.js
@@ -19,7 +19,9 @@ function ariaModalFn(...args) {
// In such case, the "Enter" key will trigger the "cancel" button instead of "ok" button, then the dialog will be closed.
// It breaks the user experience - the "Enter" key should confirm the dialog and submit the form.
// So, all "cancel" buttons without "[type]" must be marked as "type=button".
- $(el).find('form button.cancel:not([type])').attr('type', 'button');
+ for (const button of el.querySelectorAll('form button.cancel:not([type])')) {
+ button.setAttribute('type', 'button');
+ }
}
}
return ret;
diff --git a/web_src/js/modules/sortable.js b/web_src/js/modules/sortable.js
index cfe7c3bf30..1c9adb6d72 100644
--- a/web_src/js/modules/sortable.js
+++ b/web_src/js/modules/sortable.js
@@ -1,4 +1,19 @@
-export async function createSortable(...args) {
+export async function createSortable(el, opts = {}) {
const {Sortable} = await import(/* webpackChunkName: "sortablejs" */'sortablejs');
- return new Sortable(...args);
+
+ return new Sortable(el, {
+ animation: 150,
+ ghostClass: 'card-ghost',
+ onChoose: (e) => {
+ const handle = opts.handle ? e.item.querySelector(opts.handle) : e.item;
+ handle.classList.add('tw-cursor-grabbing');
+ opts.onChoose?.(e);
+ },
+ onUnchoose: (e) => {
+ const handle = opts.handle ? e.item.querySelector(opts.handle) : e.item;
+ handle.classList.remove('tw-cursor-grabbing');
+ opts.onUnchoose?.(e);
+ },
+ ...opts,
+ });
}
diff --git a/web_src/js/svg.js b/web_src/js/svg.js
index 3544b47c3d..913d26779f 100644
--- a/web_src/js/svg.js
+++ b/web_src/js/svg.js
@@ -205,7 +205,7 @@ export const SvgIcon = {
// make the <SvgIcon class="foo" class-name="bar"> classes work together
const classes = [];
- for (const cls of svgOuter.classList.values()) {
+ for (const cls of svgOuter.classList) {
classes.push(cls);
}
// TODO: drop the `className/class-name` prop in the future, only use "class" prop
@@ -213,7 +213,7 @@ export const SvgIcon = {
classes.push(...this.className.split(/\s+/).filter(Boolean));
}
if (this.symbolId) {
- classes.push('gt-hidden', 'svg-symbol-container');
+ classes.push('tw-hidden', 'svg-symbol-container');
svgInnerHtml = `<symbol id="${this.symbolId}" viewBox="${attrs['^viewBox']}">${svgInnerHtml}</symbol>`;
}
// create VNode
diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js
index 703f15d41d..a96d40ced3 100644
--- a/web_src/js/utils/dom.js
+++ b/web_src/js/utils/dom.js
@@ -22,11 +22,11 @@ function elementsCall(el, func, ...args) {
*/
function toggleShown(el, force) {
if (force === true) {
- el.classList.remove('gt-hidden');
+ el.classList.remove('tw-hidden');
} else if (force === false) {
- el.classList.add('gt-hidden');
+ el.classList.add('tw-hidden');
} else if (force === undefined) {
- el.classList.toggle('gt-hidden');
+ el.classList.toggle('tw-hidden');
} else {
throw new Error('invalid force argument');
}
@@ -46,7 +46,7 @@ export function toggleElem(el, force) {
export function isElemHidden(el) {
const res = [];
- elementsCall(el, (e) => res.push(e.classList.contains('gt-hidden')));
+ elementsCall(el, (e) => res.push(e.classList.contains('tw-hidden')));
if (res.length > 1) throw new Error(`isElemHidden doesn't work for multiple elements`);
return res[0];
}
diff --git a/web_src/js/vitest.setup.js b/web_src/js/vitest.setup.js
index 6fb0f5dc8f..5366958fb5 100644
--- a/web_src/js/vitest.setup.js
+++ b/web_src/js/vitest.setup.js
@@ -4,6 +4,7 @@ window.config = {
csrfToken: 'test-csrf-token-123456',
pageData: {},
i18n: {},
+ customEmojis: {},
appSubUrl: '',
mentionValues: [
{key: 'user1 User 1', value: 'user1', name: 'user1', fullname: 'User 1', avatar: 'https://avatar1.com'},
diff --git a/web_src/js/webcomponents/overflow-menu.js b/web_src/js/webcomponents/overflow-menu.js
index 9fa4585567..604fce7d4b 100644
--- a/web_src/js/webcomponents/overflow-menu.js
+++ b/web_src/js/webcomponents/overflow-menu.js
@@ -127,6 +127,25 @@ window.customElements.define('overflow-menu', class extends HTMLElement {
});
init() {
+ // for horizontal menus where fomantic boldens active items, prevent this bold text from
+ // enlarging the menu's active item replacing the text node with a div that renders a
+ // invisible pseudo-element that enlarges the box.
+ if (this.matches('.ui.secondary.pointing.menu, .ui.tabular.menu')) {
+ for (const item of this.querySelectorAll('.item')) {
+ for (const child of item.childNodes) {
+ if (child.nodeType === Node.TEXT_NODE) {
+ const text = child.textContent.trim(); // whitespace is insignificant inside flexbox
+ if (!text) continue;
+ const span = document.createElement('span');
+ span.classList.add('resize-for-semibold');
+ span.setAttribute('data-text', text);
+ span.textContent = text;
+ child.replaceWith(span);
+ }
+ }
+ }
+ }
+
// ResizeObserver triggers on initial render, so we don't manually call `updateItems` here which
// also avoids a full-page FOUC in Firefox that happens when `updateItems` is called too soon.
this.resizeObserver = new ResizeObserver((entries) => {
diff --git a/webpack.config.js b/webpack.config.js
index 8ab96b08ac..b6ecb29421 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -175,7 +175,6 @@ export default {
loader: 'postcss-loader',
options: {
postcssOptions: {
- map: false, // https://github.com/postcss/postcss/issues/1914
plugins: [
tailwindcssNesting(postcssNesting({edition: '2024-02'})),
tailwindcss(tailwindConfig),