From fb271d1e6a635df06e40aacd32a1652905ce69f8 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Sat, 10 Aug 2024 00:20:59 +0800 Subject: [PATCH 01/39] Add label `docs-update-needed` for PRs that modify `app.example.ini` (#31810) To help #31536. Or it's easy to forget to update https://gitea.com/gitea/docs when modifying `app.example.ini`. --- .github/labeler.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index d1b4d00d8020..265616baed49 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -75,3 +75,8 @@ modifies/js: - any-glob-to-any-file: - "**/*.js" - "**/*.vue" + +docs-update-needed: + - changed-files: + - any-glob-to-any-file: + - "custom/conf/app.example.ini" From 42841aab59640262ed3b873d86980b0bb5d869ae Mon Sep 17 00:00:00 2001 From: Jason Song Date: Sat, 10 Aug 2024 06:07:35 +0800 Subject: [PATCH 02/39] Fix typo for `LOG_COMPRESSION` in ini (#31809) Follow #31761 --------- Co-authored-by: silverwind --- custom/conf/app.example.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 30eba497b499..adec5aff3655 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2688,7 +2688,7 @@ LEVEL = Info ;; Logs retention time in days. Old logs will be deleted after this period. ;LOG_RETENTION_DAYS = 365 ;; Log compression type, `none` for no compression, `zstd` for zstd compression. -;; Other compression types like `gzip` if NOT supported, since seekable stream is required for log view. +;; Other compression types like `gzip` are NOT supported, since seekable stream is required for log view. ;; It's always recommended to use compression when using local disk as log storage if CPU or memory is not a bottleneck. ;; And for object storage services like S3, which is billed for requests, it would cause extra 2 times of get requests for each log view. ;; But it will save storage space and network bandwidth, so it's still recommended to use compression. From df27846628fc0a8a20f59fc60ca4e0107585ea05 Mon Sep 17 00:00:00 2001 From: FuXiaoHei Date: Sat, 10 Aug 2024 08:40:41 +0800 Subject: [PATCH 03/39] Show latest run when visit /run/latest (#31808) Proposal from https://github.com/go-gitea/gitea/issues/27911#issuecomment-2271982172 When visit latest run path, such as `/{user}/{repo}/actions/runs/latest`. It renders latest run instead of index=0 currently. --- models/actions/run.go | 13 +++++++++++++ routers/web/repo/actions/view.go | 29 +++++++++++++++++++---------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/models/actions/run.go b/models/actions/run.go index 4f886999e9cd..37064520a213 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -361,6 +361,19 @@ func GetRunByIndex(ctx context.Context, repoID, index int64) (*ActionRun, error) return run, nil } +func GetLatestRun(ctx context.Context, repoID int64) (*ActionRun, error) { + run := &ActionRun{ + RepoID: repoID, + } + has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).Desc("index").Get(run) + if err != nil { + return nil, err + } else if !has { + return nil, fmt.Errorf("latest run with repo_id %d: %w", repoID, util.ErrNotExist) + } + return run, nil +} + func GetWorkflowLatestRun(ctx context.Context, repoID int64, workflowFile, branch, event string) (*ActionRun, error) { var run ActionRun q := db.GetEngine(ctx).Where("repo_id=?", repoID). diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 84319fc8609b..6b422891648c 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -33,9 +33,19 @@ import ( "xorm.io/builder" ) +func getRunIndex(ctx *context_module.Context) int64 { + // if run param is "latest", get the latest run index + if ctx.PathParam("run") == "latest" { + if run, _ := actions_model.GetLatestRun(ctx, ctx.Repo.Repository.ID); run != nil { + return run.Index + } + } + return ctx.PathParamInt64("run") +} + func View(ctx *context_module.Context) { ctx.Data["PageIsActions"] = true - runIndex := ctx.PathParamInt64("run") + runIndex := getRunIndex(ctx) jobIndex := ctx.PathParamInt64("job") ctx.Data["RunIndex"] = runIndex ctx.Data["JobIndex"] = jobIndex @@ -130,7 +140,7 @@ type ViewStepLogLine struct { func ViewPost(ctx *context_module.Context) { req := web.GetForm(ctx).(*ViewRequest) - runIndex := ctx.PathParamInt64("run") + runIndex := getRunIndex(ctx) jobIndex := ctx.PathParamInt64("job") current, jobs := getRunJobs(ctx, runIndex, jobIndex) @@ -289,7 +299,7 @@ func ViewPost(ctx *context_module.Context) { // Rerun will rerun jobs in the given run // If jobIndexStr is a blank string, it means rerun all jobs func Rerun(ctx *context_module.Context) { - runIndex := ctx.PathParamInt64("run") + runIndex := getRunIndex(ctx) jobIndexStr := ctx.PathParam("job") var jobIndex int64 if jobIndexStr != "" { @@ -379,7 +389,7 @@ func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shou } func Logs(ctx *context_module.Context) { - runIndex := ctx.PathParamInt64("run") + runIndex := getRunIndex(ctx) jobIndex := ctx.PathParamInt64("job") job, _ := getRunJobs(ctx, runIndex, jobIndex) @@ -428,7 +438,7 @@ func Logs(ctx *context_module.Context) { } func Cancel(ctx *context_module.Context) { - runIndex := ctx.PathParamInt64("run") + runIndex := getRunIndex(ctx) _, jobs := getRunJobs(ctx, runIndex, -1) if ctx.Written() { @@ -469,7 +479,7 @@ func Cancel(ctx *context_module.Context) { } func Approve(ctx *context_module.Context) { - runIndex := ctx.PathParamInt64("run") + runIndex := getRunIndex(ctx) current, jobs := getRunJobs(ctx, runIndex, -1) if ctx.Written() { @@ -518,7 +528,6 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions return nil, nil } run.Repo = ctx.Repo.Repository - jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) @@ -550,7 +559,7 @@ type ArtifactsViewItem struct { } func ArtifactsView(ctx *context_module.Context) { - runIndex := ctx.PathParamInt64("run") + runIndex := getRunIndex(ctx) run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) if err != nil { if errors.Is(err, util.ErrNotExist) { @@ -588,7 +597,7 @@ func ArtifactsDeleteView(ctx *context_module.Context) { return } - runIndex := ctx.PathParamInt64("run") + runIndex := getRunIndex(ctx) artifactName := ctx.PathParam("artifact_name") run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) @@ -606,7 +615,7 @@ func ArtifactsDeleteView(ctx *context_module.Context) { } func ArtifactsDownloadView(ctx *context_module.Context) { - runIndex := ctx.PathParamInt64("run") + runIndex := getRunIndex(ctx) artifactName := ctx.PathParam("artifact_name") run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) From 9633f336c87947dc7d2a5e76077a10699ba5e50d Mon Sep 17 00:00:00 2001 From: a1012112796 <1012112796@qq.com> Date: Sat, 10 Aug 2024 09:09:34 +0800 Subject: [PATCH 04/39] Add warning message in merge instructions when `AutodetectManualMerge` was not enabled (#31805) not enabled quick-f-i-x https://github.com/go-gitea/gitea/issues/31433 ? , maybe need more disscusion about better solutions. example view: ![image](https://github.com/user-attachments/assets/2af7e1e8-42b9-4473-89c7-12d4a9205d3f) adtion notes about how to enable `AutodetectManualMerge` ![image](https://github.com/user-attachments/assets/28f84317-367a-40d8-b50d-a19ef7c664d4) Signed-off-by: a1012112796 <1012112796@qq.com> --- options/locale/locale_en-US.ini | 1 + routers/web/repo/issue.go | 2 ++ .../repo/issue/view_content/pull_merge_instruction.tmpl | 8 +++++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index cca068a3a28c..28b3df6c49ac 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1891,6 +1891,7 @@ pulls.cmd_instruction_checkout_title = Checkout pulls.cmd_instruction_checkout_desc = From your project repository, check out a new branch and test the changes. pulls.cmd_instruction_merge_title = Merge pulls.cmd_instruction_merge_desc = Merge the changes and update on Gitea. +pulls.cmd_instruction_merge_warning = Warning: This operation can not merge pull request because "autodetect manual merge" was not enable pulls.clear_merge_message = Clear merge message pulls.clear_merge_message_hint = Clearing the merge message will only remove the commit message content and keep generated git trailers such as "Co-Authored-By …". diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 4773cc9adcea..691de94290f0 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -1869,6 +1869,8 @@ func ViewIssue(ctx *context.Context) { } prConfig := prUnit.PullRequestsConfig() + ctx.Data["AutodetectManualMerge"] = prConfig.AutodetectManualMerge + var mergeStyle repo_model.MergeStyle // Check correct values and select default if ms, ok := ctx.Data["MergeStyle"].(repo_model.MergeStyle); !ok || diff --git a/templates/repo/issue/view_content/pull_merge_instruction.tmpl b/templates/repo/issue/view_content/pull_merge_instruction.tmpl index bb59b497190b..9a3e2cb7d755 100644 --- a/templates/repo/issue/view_content/pull_merge_instruction.tmpl +++ b/templates/repo/issue/view_content/pull_merge_instruction.tmpl @@ -15,7 +15,13 @@
git checkout {{$localBranch}}
{{if .ShowMergeInstructions}} -

{{ctx.Locale.Tr "repo.pulls.cmd_instruction_merge_title"}}

{{ctx.Locale.Tr "repo.pulls.cmd_instruction_merge_desc"}}
+
+

{{ctx.Locale.Tr "repo.pulls.cmd_instruction_merge_title"}}

+ {{ctx.Locale.Tr "repo.pulls.cmd_instruction_merge_desc"}} + {{if not .AutodetectManualMerge}} +
{{ctx.Locale.Tr "repo.pulls.cmd_instruction_merge_warning"}}
+ {{end}} +
git checkout {{.PullRequest.BaseBranch}}
From 32075d28803344230e6366e2a683b8d3f39b2433 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 10 Aug 2024 11:46:48 +0200 Subject: [PATCH 05/39] Add types to various low-level functions (#31781) Adds types to various low-level modules. All changes are type-only, no runtime changes. `tsc` now reports 38 less errors. One problem was that `@types/sortablejs` does not accept promise return in its functions which triggered the linter, so I disabled the rules on those line. --- package-lock.json | 22 ++++++-- package.json | 1 + types.d.ts | 9 ++++ web_src/js/features/dropzone.ts | 2 +- web_src/js/features/repo-issue-list.ts | 2 +- web_src/js/features/repo-projects.ts | 6 +-- web_src/js/features/stopwatch.ts | 2 +- web_src/js/modules/dirauto.ts | 14 +++-- web_src/js/modules/sortable.ts | 6 ++- web_src/js/modules/stores.ts | 3 +- web_src/js/modules/tippy.ts | 47 ++++++++-------- web_src/js/utils/color.ts | 11 ++-- web_src/js/utils/dom.ts | 75 +++++++++++++++----------- 13 files changed, 123 insertions(+), 77 deletions(-) diff --git a/package-lock.json b/package-lock.json index 846cf6f838b8..5c56531ec058 100644 --- a/package-lock.json +++ b/package-lock.json @@ -108,6 +108,7 @@ "stylelint-declaration-strict-value": "1.10.6", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.3.2", + "type-fest": "4.23.0", "updates": "16.3.7", "vite-string-plugin": "1.3.4", "vitest": "2.0.5" @@ -7439,6 +7440,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -12287,13 +12301,13 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.23.0.tgz", + "integrity": "sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/package.json b/package.json index 730c47f90de2..d1a624f11692 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "stylelint-declaration-strict-value": "1.10.6", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.3.2", + "type-fest": "4.23.0", "updates": "16.3.7", "vite-string-plugin": "1.3.4", "vitest": "2.0.5" diff --git a/types.d.ts b/types.d.ts index 3da7cbe05039..a8dc09e06436 100644 --- a/types.d.ts +++ b/types.d.ts @@ -3,6 +3,11 @@ declare module '*.svg' { export default value; } +declare module '*.css' { + const value: string; + export default value; +} + declare let __webpack_public_path__: string; interface Window { @@ -20,3 +25,7 @@ declare module 'htmx.org/dist/htmx.esm.js' { const value = await import('htmx.org'); export default value; } + +interface Element { + _tippy: import('tippy.js').Instance; +} diff --git a/web_src/js/features/dropzone.ts b/web_src/js/features/dropzone.ts index 392bc1db660c..f652af045615 100644 --- a/web_src/js/features/dropzone.ts +++ b/web_src/js/features/dropzone.ts @@ -52,7 +52,7 @@ function addCopyLink(file) { copyLinkEl.addEventListener('click', async (e) => { e.preventDefault(); const success = await clippie(generateMarkdownLinkForAttachment(file)); - showTemporaryTooltip(e.target, success ? i18n.copy_success : i18n.copy_error); + showTemporaryTooltip(e.target as Element, success ? i18n.copy_success : i18n.copy_error); }); file.previewTemplate.append(copyLinkEl); } diff --git a/web_src/js/features/repo-issue-list.ts b/web_src/js/features/repo-issue-list.ts index 1e4a880f2ef0..134304617be5 100644 --- a/web_src/js/features/repo-issue-list.ts +++ b/web_src/js/features/repo-issue-list.ts @@ -196,7 +196,7 @@ async function initIssuePinSort() { createSortable(pinDiv, { group: 'shared', - onEnd: pinMoveEnd, + onEnd: pinMoveEnd, // eslint-disable-line @typescript-eslint/no-misused-promises }); } diff --git a/web_src/js/features/repo-projects.ts b/web_src/js/features/repo-projects.ts index 950d78fec717..bc2bb69a339b 100644 --- a/web_src/js/features/repo-projects.ts +++ b/web_src/js/features/repo-projects.ts @@ -60,7 +60,7 @@ async function initRepoProjectSortable() { handle: '.project-column-header', delayOnTouchOnly: true, delay: 500, - onSort: async () => { + onSort: async () => { // eslint-disable-line @typescript-eslint/no-misused-promises boardColumns = mainBoard.querySelectorAll('.project-column'); const columnSorting = { @@ -84,8 +84,8 @@ async function initRepoProjectSortable() { const boardCardList = boardColumn.querySelectorAll('.cards')[0]; createSortable(boardCardList, { group: 'shared', - onAdd: moveIssue, - onUpdate: moveIssue, + onAdd: moveIssue, // eslint-disable-line @typescript-eslint/no-misused-promises + onUpdate: moveIssue, // eslint-disable-line @typescript-eslint/no-misused-promises delayOnTouchOnly: true, delay: 500, }); diff --git a/web_src/js/features/stopwatch.ts b/web_src/js/features/stopwatch.ts index d89aa4bfac49..af52be4e24e8 100644 --- a/web_src/js/features/stopwatch.ts +++ b/web_src/js/features/stopwatch.ts @@ -27,7 +27,7 @@ export function initStopwatch() { stopwatchEl.removeAttribute('href'); // intended for noscript mode only createTippy(stopwatchEl, { - content: stopwatchPopup.cloneNode(true), + content: stopwatchPopup.cloneNode(true) as Element, placement: 'bottom-end', trigger: 'click', maxWidth: 'none', diff --git a/web_src/js/modules/dirauto.ts b/web_src/js/modules/dirauto.ts index 855bae1ca872..db45a9cd17d4 100644 --- a/web_src/js/modules/dirauto.ts +++ b/web_src/js/modules/dirauto.ts @@ -1,7 +1,9 @@ import {isDocumentFragmentOrElementNode} from '../utils/dom.ts'; +type DirElement = HTMLInputElement | HTMLTextAreaElement; + // for performance considerations, it only uses performant syntax -function attachDirAuto(el) { +function attachDirAuto(el: DirElement) { if (el.type !== 'hidden' && el.type !== 'checkbox' && el.type !== 'radio' && @@ -18,10 +20,12 @@ export function initDirAuto() { const mutation = mutationList[i]; const len = mutation.addedNodes.length; for (let i = 0; i < len; i++) { - const addedNode = mutation.addedNodes[i]; + const addedNode = mutation.addedNodes[i] as HTMLElement; if (!isDocumentFragmentOrElementNode(addedNode)) continue; - if (addedNode.nodeName === 'INPUT' || addedNode.nodeName === 'TEXTAREA') attachDirAuto(addedNode); - const children = addedNode.querySelectorAll('input, textarea'); + if (addedNode.nodeName === 'INPUT' || addedNode.nodeName === 'TEXTAREA') { + attachDirAuto(addedNode as DirElement); + } + const children = addedNode.querySelectorAll('input, textarea'); const len = children.length; for (let childIdx = 0; childIdx < len; childIdx++) { attachDirAuto(children[childIdx]); @@ -30,7 +34,7 @@ export function initDirAuto() { } }); - const docNodes = document.querySelectorAll('input, textarea'); + const docNodes = document.querySelectorAll('input, textarea'); const len = docNodes.length; for (let i = 0; i < len; i++) { attachDirAuto(docNodes[i]); diff --git a/web_src/js/modules/sortable.ts b/web_src/js/modules/sortable.ts index 1c9adb6d72d5..460f4c6d912d 100644 --- a/web_src/js/modules/sortable.ts +++ b/web_src/js/modules/sortable.ts @@ -1,4 +1,6 @@ -export async function createSortable(el, opts = {}) { +import type {SortableOptions} from 'sortablejs'; + +export async function createSortable(el, opts: {handle?: string} & SortableOptions = {}) { const {Sortable} = await import(/* webpackChunkName: "sortablejs" */'sortablejs'); return new Sortable(el, { @@ -15,5 +17,5 @@ export async function createSortable(el, opts = {}) { opts.onUnchoose?.(e); }, ...opts, - }); + } satisfies SortableOptions); } diff --git a/web_src/js/modules/stores.ts b/web_src/js/modules/stores.ts index 1a0ed7eda14f..942a7bc5086f 100644 --- a/web_src/js/modules/stores.ts +++ b/web_src/js/modules/stores.ts @@ -1,6 +1,7 @@ import {reactive} from 'vue'; +import type {Reactive} from 'vue'; -let diffTreeStoreReactive; +let diffTreeStoreReactive: Reactive>; export function diffTreeStore() { if (!diffTreeStoreReactive) { diffTreeStoreReactive = reactive(window.config.pageData.diffFileInfo); diff --git a/web_src/js/modules/tippy.ts b/web_src/js/modules/tippy.ts index a18bad5db796..375d816c6bc2 100644 --- a/web_src/js/modules/tippy.ts +++ b/web_src/js/modules/tippy.ts @@ -1,16 +1,22 @@ import tippy, {followCursor} from 'tippy.js'; import {isDocumentFragmentOrElementNode} from '../utils/dom.ts'; import {formatDatetime} from '../utils/time.ts'; +import type {Content, Instance, Props} from 'tippy.js'; -const visibleInstances = new Set(); +type TippyOpts = { + role?: string, + theme?: 'default' | 'tooltip' | 'menu' | 'box-with-header' | 'bare', +} & Partial; + +const visibleInstances = new Set(); const arrowSvg = ``; -export function createTippy(target, opts = {}) { +export function createTippy(target: Element, opts: TippyOpts = {}) { // the callback functions should be destructured from opts, // because we should use our own wrapper functions to handle them, do not let the user override them const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts; - const instance = tippy(target, { + const instance: Instance = tippy(target, { appendTo: document.body, animation: false, allowHTML: false, @@ -18,15 +24,15 @@ export function createTippy(target, opts = {}) { interactiveBorder: 20, ignoreAttributes: true, maxWidth: 500, // increase over default 350px - onHide: (instance) => { + onHide: (instance: Instance) => { visibleInstances.delete(instance); return onHide?.(instance); }, - onDestroy: (instance) => { + onDestroy: (instance: Instance) => { visibleInstances.delete(instance); return onDestroy?.(instance); }, - onShow: (instance) => { + onShow: (instance: Instance) => { // hide other tooltip instances so only one tooltip shows at a time for (const visibleInstance of visibleInstances) { if (visibleInstance.props.role === 'tooltip') { @@ -43,7 +49,7 @@ export function createTippy(target, opts = {}) { theme: theme || role || 'default', plugins: [followCursor], ...other, - }); + } satisfies Partial); if (role === 'menu') { target.setAttribute('aria-haspopup', 'true'); @@ -58,12 +64,8 @@ export function createTippy(target, opts = {}) { * If the target element has no content, then no tooltip will be attached, and it returns null. * * Note: "tooltip" doesn't equal to "tippy". "tooltip" means a auto-popup content, it just uses tippy as the implementation. - * - * @param target {HTMLElement} - * @param content {null|string} - * @returns {null|tippy} */ -function attachTooltip(target, content = null) { +function attachTooltip(target: Element, content: Content = null) { switchTitleToTooltip(target); content = content ?? target.getAttribute('data-tooltip-content'); @@ -84,7 +86,7 @@ function attachTooltip(target, content = null) { placement: target.getAttribute('data-tooltip-placement') || 'top-start', followCursor: target.getAttribute('data-tooltip-follow-cursor') || false, ...(target.getAttribute('data-tooltip-interactive') === 'true' ? {interactive: true, aria: {content: 'describedby', expanded: false}} : {}), - }; + } as TippyOpts; if (!target._tippy) { createTippy(target, props); @@ -94,7 +96,7 @@ function attachTooltip(target, content = null) { return target._tippy; } -function switchTitleToTooltip(target) { +function switchTitleToTooltip(target: Element) { let title = target.getAttribute('title'); if (title) { // apply custom formatting to relative-time's tooltips @@ -118,16 +120,15 @@ function switchTitleToTooltip(target) { * According to https://www.w3.org/TR/DOM-Level-3-Events/#events-mouseevent-event-order , mouseover event is fired before mouseenter event * Some browsers like PaleMoon don't support "addEventListener('mouseenter', capture)" * The tippy by default uses "mouseenter" event to show, so we use "mouseover" event to switch to tippy - * @param e {Event} */ -function lazyTooltipOnMouseHover(e) { +function lazyTooltipOnMouseHover(e: MouseEvent) { e.target.removeEventListener('mouseover', lazyTooltipOnMouseHover, true); attachTooltip(this); } // Activate the tooltip for current element. // If the element has no aria-label, use the tooltip content as aria-label. -function attachLazyTooltip(el) { +function attachLazyTooltip(el: Element) { el.addEventListener('mouseover', lazyTooltipOnMouseHover, {capture: true}); // meanwhile, if the element has no aria-label, use the tooltip content as aria-label @@ -140,15 +141,15 @@ function attachLazyTooltip(el) { } // Activate the tooltip for all children elements. -function attachChildrenLazyTooltip(target) { - for (const el of target.querySelectorAll('[data-tooltip-content]')) { +function attachChildrenLazyTooltip(target: Element) { + for (const el of target.querySelectorAll('[data-tooltip-content]')) { attachLazyTooltip(el); } } export function initGlobalTooltips() { // use MutationObserver to detect new "data-tooltip-content" elements added to the DOM, or attributes changed - const observerConnect = (observer) => observer.observe(document, { + const observerConnect = (observer: MutationObserver) => observer.observe(document, { subtree: true, childList: true, attributeFilter: ['data-tooltip-content', 'title'], @@ -159,7 +160,7 @@ export function initGlobalTooltips() { for (const mutation of [...mutationList, ...pending]) { if (mutation.type === 'childList') { // mainly for Vue components and AJAX rendered elements - for (const el of mutation.addedNodes) { + for (const el of mutation.addedNodes as NodeListOf) { if (!isDocumentFragmentOrElementNode(el)) continue; attachChildrenLazyTooltip(el); if (el.hasAttribute('data-tooltip-content')) { @@ -167,7 +168,7 @@ export function initGlobalTooltips() { } } } else if (mutation.type === 'attributes') { - attachTooltip(mutation.target); + attachTooltip(mutation.target as Element); } } observerConnect(observer); @@ -177,7 +178,7 @@ export function initGlobalTooltips() { attachChildrenLazyTooltip(document.documentElement); } -export function showTemporaryTooltip(target, content) { +export function showTemporaryTooltip(target: Element, content: Content) { // if the target is inside a dropdown, don't show the tooltip because when the dropdown // closes, the tippy would be pushed unsightly to the top-left of the screen like seen // on the issue comment menu. diff --git a/web_src/js/utils/color.ts b/web_src/js/utils/color.ts index 198f97c454e9..3ee32395fb3d 100644 --- a/web_src/js/utils/color.ts +++ b/web_src/js/utils/color.ts @@ -1,26 +1,27 @@ import tinycolor from 'tinycolor2'; +import type {ColorInput} from 'tinycolor2'; // Returns relative luminance for a SRGB color - https://en.wikipedia.org/wiki/Relative_luminance // Keep this in sync with modules/util/color.go -function getRelativeLuminance(color) { +function getRelativeLuminance(color: ColorInput) { const {r, g, b} = tinycolor(color).toRgb(); return (0.2126729 * r + 0.7151522 * g + 0.072175 * b) / 255; } -function useLightText(backgroundColor) { +function useLightText(backgroundColor: ColorInput) { return getRelativeLuminance(backgroundColor) < 0.453; } // Given a background color, returns a black or white foreground color that the highest // contrast ratio. In the future, the APCA contrast function, or CSS `contrast-color` will be better. // https://github.com/color-js/color.js/blob/eb7b53f7a13bb716ec8b28c7a56f052cd599acd9/src/contrast/APCA.js#L42 -export function contrastColor(backgroundColor) { +export function contrastColor(backgroundColor: ColorInput) { return useLightText(backgroundColor) ? '#fff' : '#000'; } -function resolveColors(obj) { +function resolveColors(obj: Record) { const styles = window.getComputedStyle(document.documentElement); - const getColor = (name) => styles.getPropertyValue(name).trim(); + const getColor = (name: string) => styles.getPropertyValue(name).trim(); return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, getColor(value)])); } diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts index 82e7b755a5cb..5fc218319497 100644 --- a/web_src/js/utils/dom.ts +++ b/web_src/js/utils/dom.ts @@ -1,14 +1,21 @@ import {debounce} from 'throttle-debounce'; +import type {Promisable} from 'type-fest'; +import type $ from 'jquery'; -function elementsCall(el, func, ...args) { +type ElementArg = Element | string | NodeListOf | Array | ReturnType; +type ElementsCallback = (el: Element) => Promisable; +type ElementsCallbackWithArgs = (el: Element, ...args: any[]) => Promisable; +type IterableElements = NodeListOf | Array; + +function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]) { if (typeof el === 'string' || el instanceof String) { - el = document.querySelectorAll(el); + el = document.querySelectorAll(el as string); } if (el instanceof Node) { func(el, ...args); } else if (el.length !== undefined) { // this works for: NodeList, HTMLCollection, Array, jQuery - for (const e of el) { + for (const e of (el as IterableElements)) { func(e, ...args); } } else { @@ -17,10 +24,10 @@ function elementsCall(el, func, ...args) { } /** - * @param el string (selector), Node, NodeList, HTMLCollection, Array or jQuery + * @param el Element * @param force force=true to show or force=false to hide, undefined to toggle */ -function toggleShown(el, force) { +function toggleShown(el: Element, force: boolean) { if (force === true) { el.classList.remove('tw-hidden'); } else if (force === false) { @@ -32,26 +39,26 @@ function toggleShown(el, force) { } } -export function showElem(el) { +export function showElem(el: ElementArg) { elementsCall(el, toggleShown, true); } -export function hideElem(el) { +export function hideElem(el: ElementArg) { elementsCall(el, toggleShown, false); } -export function toggleElem(el, force) { +export function toggleElem(el: ElementArg, force?: boolean) { elementsCall(el, toggleShown, force); } -export function isElemHidden(el) { - const res = []; +export function isElemHidden(el: ElementArg) { + const res: boolean[] = []; 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]; } -function applyElemsCallback(elems, fn) { +function applyElemsCallback(elems: IterableElements, fn?: ElementsCallback) { if (fn) { for (const el of elems) { fn(el); @@ -60,20 +67,22 @@ function applyElemsCallback(elems, fn) { return elems; } -export function queryElemSiblings(el, selector = '*', fn) { - return applyElemsCallback(Array.from(el.parentNode.children).filter((child) => child !== el && child.matches(selector)), fn); +export function queryElemSiblings(el: Element, selector = '*', fn?: ElementsCallback) { + return applyElemsCallback(Array.from(el.parentNode.children).filter((child: Element) => { + return child !== el && child.matches(selector); + }), fn); } // it works like jQuery.children: only the direct children are selected -export function queryElemChildren(parent, selector = '*', fn) { +export function queryElemChildren(parent: Element | ParentNode, selector = '*', fn?: ElementsCallback) { return applyElemsCallback(parent.querySelectorAll(`:scope > ${selector}`), fn); } -export function queryElems(selector, fn) { +export function queryElems(selector: string, fn?: ElementsCallback) { return applyElemsCallback(document.querySelectorAll(selector), fn); } -export function onDomReady(cb) { +export function onDomReady(cb: () => Promisable) { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', cb); } else { @@ -83,7 +92,7 @@ export function onDomReady(cb) { // checks whether an element is owned by the current document, and whether it is a document fragment or element node // if it is, it means it is a "normal" element managed by us, which can be modified safely. -export function isDocumentFragmentOrElementNode(el) { +export function isDocumentFragmentOrElementNode(el: Element) { try { return el.ownerDocument === document && el.nodeType === Node.ELEMENT_NODE || el.nodeType === Node.DOCUMENT_FRAGMENT_NODE; } catch { @@ -108,12 +117,15 @@ export function isDocumentFragmentOrElementNode(el) { // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // --------------------------------------------------------------------- -export function autosize(textarea, {viewportMarginBottom = 0} = {}) { +export function autosize(textarea: HTMLTextAreaElement, {viewportMarginBottom = 0}: {viewportMarginBottom?: number} = {}) { let isUserResized = false; // lastStyleHeight and initialStyleHeight are CSS values like '100px' - let lastMouseX, lastMouseY, lastStyleHeight, initialStyleHeight; + let lastMouseX: number; + let lastMouseY: number; + let lastStyleHeight: string; + let initialStyleHeight: string; - function onUserResize(event) { + function onUserResize(event: MouseEvent) { if (isUserResized) return; if (lastMouseX !== event.clientX || lastMouseY !== event.clientY) { const newStyleHeight = textarea.style.height; @@ -133,7 +145,7 @@ export function autosize(textarea, {viewportMarginBottom = 0} = {}) { while (el !== document.body && el !== null) { offsetTop += el.offsetTop || 0; - el = el.offsetParent; + el = el.offsetParent as HTMLTextAreaElement; } const top = offsetTop - document.defaultView.scrollY; @@ -213,14 +225,15 @@ export function autosize(textarea, {viewportMarginBottom = 0} = {}) { }; } -export function onInputDebounce(fn) { +export function onInputDebounce(fn: () => Promisable) { return debounce(300, fn); } +type LoadableElement = HTMLEmbedElement | HTMLIFrameElement | HTMLImageElement | HTMLScriptElement | HTMLTrackElement; + // Set the `src` attribute on an element and returns a promise that resolves once the element -// has loaded or errored. Suitable for all elements mention in: -// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/load_event -export function loadElem(el, src) { +// has loaded or errored. +export function loadElem(el: LoadableElement, src: string) { return new Promise((resolve) => { el.addEventListener('load', () => resolve(true), {once: true}); el.addEventListener('error', () => resolve(false), {once: true}); @@ -256,14 +269,14 @@ export function initSubmitEventPolyfill() { * @param {HTMLElement} element The element to check. * @returns {boolean} True if the element is visible. */ -export function isElemVisible(element) { +export function isElemVisible(element: HTMLElement) { if (!element) return false; return Boolean(element.offsetWidth || element.offsetHeight || element.getClientRects().length); } // replace selected text in a textarea while preserving editor history, e.g. CTRL-Z works after this -export function replaceTextareaSelection(textarea, text) { +export function replaceTextareaSelection(textarea: HTMLTextAreaElement, text: string) { const before = textarea.value.slice(0, textarea.selectionStart ?? undefined); const after = textarea.value.slice(textarea.selectionEnd ?? undefined); let success = true; @@ -287,13 +300,13 @@ export function replaceTextareaSelection(textarea, text) { } // Warning: Do not enter any unsanitized variables here -export function createElementFromHTML(htmlString) { +export function createElementFromHTML(htmlString: string) { const div = document.createElement('div'); div.innerHTML = htmlString.trim(); - return div.firstChild; + return div.firstChild as Element; } -export function createElementFromAttrs(tagName, attrs) { +export function createElementFromAttrs(tagName: string, attrs: Record) { const el = document.createElement(tagName); for (const [key, value] of Object.entries(attrs)) { if (value === undefined || value === null) continue; @@ -307,7 +320,7 @@ export function createElementFromAttrs(tagName, attrs) { return el; } -export function animateOnce(el, animationClassName) { +export function animateOnce(el: Element, animationClassName: string): Promise { return new Promise((resolve) => { el.addEventListener('animationend', function onAnimationEnd() { el.classList.remove(animationClassName); From ff1779d7cf6d4adb04d9692f63948e83bc7a9022 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Sun, 11 Aug 2024 00:31:26 +0000 Subject: [PATCH 06/39] [skip ci] Updated translations via Crowdin --- options/locale/locale_pt-PT.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 89cb776b6982..5526a00fc302 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1475,6 +1475,7 @@ issues.remove_labels=removeu os rótulos %s %s issues.add_remove_labels=adicionou o(s) rótulo(s) %s e removeu %s %s issues.add_milestone_at=`adicionou esta questão à etapa %s %s` issues.add_project_at=`adicionou esta questão ao planeamento %s %s` +issues.move_to_column_of_project=`isto foi movido para %s dentro de %s em %s` issues.change_milestone_at=`modificou a etapa de %s para %s %s` issues.change_project_at=`modificou o planeamento de %s para %s %s` issues.remove_milestone_at=`removeu esta questão da etapa %s %s` From e45a4c98292bf7c53700ff2f6f8e4dc7ba2e3e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Barkoski?= <65479069+fabiobarkoski@users.noreply.github.com> Date: Sun, 11 Aug 2024 01:50:54 -0300 Subject: [PATCH 07/39] Move repository visibility to danger zone in the settings area (#31126) Moved repository visibility to the danger zone in the settings area. To change the visibility, it is necessary to go to the danger zone, click on the private/public button, and accept the change in the modal. Resolves: #23826 --- ## Screenshots
Before Private repo: ![Private repo](https://github.com/go-gitea/gitea/assets/65479069/4313492a-4854-48bc-9f47-974e3539d791) Public repo: ![Public repo](https://github.com/go-gitea/gitea/assets/65479069/1c45f6e4-ee93-4799-9331-e9d4a7e0f16a)
After Make private: ![Screenshot from 2024-05-28 21-35-38](https://github.com/go-gitea/gitea/assets/65479069/4887e28a-0514-4990-aa69-bf3ddc7e6c7d) Make private modal ![Screenshot from 2024-06-13 23-55-55](https://github.com/go-gitea/gitea/assets/65479069/9f5a7604-069b-41a2-973b-ee2d58e85953) ![Screenshot from 2024-06-13 23-53-09](https://github.com/go-gitea/gitea/assets/65479069/06c22726-eab2-4bce-8df7-62849dcce974) Make public: ![Screenshot from 2024-05-28 21-34-27](https://github.com/go-gitea/gitea/assets/65479069/6d388f99-0356-48a0-9d85-320cdba55179) Make public modal ![Screenshot from 2024-06-13 23-53-37](https://github.com/go-gitea/gitea/assets/65479069/8944972e-f2d4-4aea-ba96-b892febb5ced)
--------- Co-authored-by: Kemal Zebari <60799661+kemzeb@users.noreply.github.com> --- options/locale/locale_en-US.ini | 12 ++++++ routers/web/repo/setting/setting.go | 43 +++++++++++++++---- services/repository/repository.go | 25 +++++++++++ templates/repo/settings/options.tmpl | 63 +++++++++++++++++++++------- 4 files changed, 120 insertions(+), 23 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 28b3df6c49ac..b30504edd796 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2466,6 +2466,18 @@ settings.thread_id = Thread ID settings.matrix.homeserver_url = Homeserver URL settings.matrix.room_id = Room ID settings.matrix.message_type = Message Type +settings.visibility.private.button = Make Private +settings.visibility.private.text = Changing the visibility to private will not only make the repo visible to only allowed members but may remove the relation between it and forks, watchers, and stars. +settings.visibility.private.bullet_title = Changing the visibility to private will: +settings.visibility.private.bullet_one = Make the repo visible to only allowed members. +settings.visibility.private.bullet_two = May remove the relation between it and forks, watchers, and stars. +settings.visibility.public.button = Make Public +settings.visibility.public.text = Changing the visibility to public will make the repo visible to anyone. +settings.visibility.public.bullet_title= Changing the visibility to public will: +settings.visibility.public.bullet_one = Make the repo visible to anyone. +settings.visibility.success = Repository visibility changed. +settings.visibility.error = An error occurred while trying to change the repo visibility. +settings.visibility.fork_error = Can't change the visibility of a forked repo. settings.archive.button = Archive Repo settings.archive.header = Archive This Repo settings.archive.text = Archiving the repo will make it entirely read-only. It will be hidden from the dashboard. Nobody (not even you!) will be able to make new commits, or open any issues or pull requests. diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 1e0349cdeec4..3f9140857ad8 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -170,15 +170,7 @@ func SettingsPost(ctx *context.Context) { form.Private = repo.BaseRepo.IsPrivate || repo.BaseRepo.Owner.Visibility == structs.VisibleTypePrivate } - visibilityChanged := repo.IsPrivate != form.Private - // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public - if visibilityChanged && setting.Repository.ForcePrivate && !form.Private && !ctx.Doer.IsAdmin { - ctx.RenderWithErr(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form) - return - } - - repo.IsPrivate = form.Private - if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil { + if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { ctx.ServerError("UpdateRepository", err) return } @@ -940,6 +932,39 @@ func SettingsPost(ctx *context.Context) { log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) ctx.Redirect(ctx.Repo.RepoLink + "/settings") + case "visibility": + if repo.IsFork { + ctx.Flash.Error(ctx.Tr("repo.settings.visibility.fork_error")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") + return + } + + var err error + + // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public + if setting.Repository.ForcePrivate && repo.IsPrivate && !ctx.Doer.IsAdmin { + ctx.RenderWithErr(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form) + return + } + + if repo.IsPrivate { + err = repo_service.MakeRepoPublic(ctx, repo) + } else { + err = repo_service.MakeRepoPrivate(ctx, repo) + } + + if err != nil { + log.Error("Tried to change the visibility of the repo: %s", err) + ctx.Flash.Error(ctx.Tr("repo.settings.visibility.error")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") + return + } + + ctx.Flash.Success(ctx.Tr("repo.settings.visibility.success")) + + log.Trace("Repository visibility changed: %s/%s", ctx.Repo.Owner.Name, repo.Name) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") + default: ctx.NotFound("", nil) } diff --git a/services/repository/repository.go b/services/repository/repository.go index b7aac3cfe0d8..5306e7d45cce 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -122,6 +122,31 @@ func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibili return committer.Commit() } +func UpdateRepositoryVisibility(ctx context.Context, repo *repo_model.Repository, isPrivate bool) (err error) { + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + + defer committer.Close() + + repo.IsPrivate = isPrivate + + if err = repo_module.UpdateRepository(ctx, repo, true); err != nil { + return fmt.Errorf("UpdateRepositoryVisibility: %w", err) + } + + return committer.Commit() +} + +func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error) { + return UpdateRepositoryVisibility(ctx, repo, false) +} + +func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository) (err error) { + return UpdateRepositoryVisibility(ctx, repo, true) +} + // LinkedRepository returns the linked repo if any func LinkedRepository(ctx context.Context, a *repo_model.Attachment) (*repo_model.Repository, unit.Type, error) { if a.IssueID != 0 { diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 4f98133df367..f12bbbdf4a7f 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -23,20 +23,6 @@
- {{if not .Repository.IsFork}} -
- -
- {{if .IsAdmin}} - - {{else}} - - {{if and .Repository.IsPrivate $.ForcePrivate}}{{end}} - {{end}} - -
-
- {{end}}
@@ -786,6 +772,27 @@
+ {{if not .Repository.IsFork}} +
+
+
{{ctx.Locale.Tr "repo.visibility"}}
+ {{if .Repository.IsPrivate}} +
{{ctx.Locale.Tr "repo.settings.visibility.public.text"}}
+ {{else}} +
{{ctx.Locale.Tr "repo.settings.visibility.private.text"}}
+ {{end}} +
+
+ +
+
+ {{end}} {{if .Repository.IsMirror}}
@@ -1012,6 +1019,34 @@
+ {{if not .Repository.IsFork}} + + {{end}} + {{if .Repository.UnitEnabled $.Context ctx.Consts.RepoUnitTypeWiki}}
- + {{ctx.Locale.Tr "repo.issues.new.no_assignees"}}
{{range .Assignees}} - + {{ctx.AvatarUtils.Avatar . 28 "tw-mr-2 tw-align-middle"}}{{.GetDisplayName}} {{end}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 18ea4a62b58f..52d37547374a 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -22345,6 +22345,9 @@ "type": "string", "x-go-name": "About" }, + "assignees": { + "$ref": "#/definitions/IssueTemplateStringSlice" + }, "body": { "type": "array", "items": { @@ -22361,7 +22364,7 @@ "x-go-name": "FileName" }, "labels": { - "$ref": "#/definitions/IssueTemplateLabels" + "$ref": "#/definitions/IssueTemplateStringSlice" }, "name": { "type": "string", @@ -22378,7 +22381,7 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, - "IssueTemplateLabels": { + "IssueTemplateStringSlice": { "type": "array", "items": { "type": "string" From fe7c9416777243264e8482d3af29e30c2b671074 Mon Sep 17 00:00:00 2001 From: Simon Priet <105607989+SimonPistache@users.noreply.github.com> Date: Tue, 13 Aug 2024 01:36:28 +0200 Subject: [PATCH 12/39] Scroll images in project issues separately from the remaining issue (#31683) As discussed in #31667 & #26561, when a card on a Project contains images, they can overflow the card on its containing column. This aims to fix this issue via snapping scrollbars. --- Issue #31667 is open to discussion as there should be room for improvement. --- web_src/css/features/projects.css | 7 ++++++- web_src/css/repo/issue-card.css | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/web_src/css/features/projects.css b/web_src/css/features/projects.css index 151b0a23d9dc..4a0205c910a3 100644 --- a/web_src/css/features/projects.css +++ b/web_src/css/features/projects.css @@ -70,7 +70,9 @@ .card-attachment-images { display: inline-block; white-space: nowrap; - overflow: hidden; + overflow: scroll; + cursor: default; + scroll-snap-type: x mandatory; text-align: center; } @@ -78,7 +80,10 @@ display: inline-block; max-height: 50px; border-radius: var(--border-radius); + text-align: left; + scroll-snap-align: center; margin-right: 2px; + aspect-ratio: 1; } .card-attachment-images img:only-child { diff --git a/web_src/css/repo/issue-card.css b/web_src/css/repo/issue-card.css index 390bfb6a0141..fb832bd05ab0 100644 --- a/web_src/css/repo/issue-card.css +++ b/web_src/css/repo/issue-card.css @@ -2,7 +2,7 @@ display: flex; flex-direction: column; gap: 4px; - align-items: start; + align-items: stretch; border-radius: var(--border-radius); padding: 8px 10px; border: 1px solid var(--color-secondary); From 5bcab0b702274e137ff3e40f3ffa9078605fc2a2 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Tue, 13 Aug 2024 00:28:59 +0000 Subject: [PATCH 13/39] [skip ci] Updated translations via Crowdin --- options/locale/locale_ja-JP.ini | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index e4348d302487..b36b32043963 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -2868,6 +2868,7 @@ dashboard.reinit_missing_repos=レコードが存在するが見当たらない dashboard.sync_external_users=外部ユーザーデータの同期 dashboard.cleanup_hook_task_table=hook_taskテーブルのクリーンアップ dashboard.cleanup_packages=期限切れパッケージのクリーンアップ +dashboard.cleanup_actions=期限切れのActionsリソースのクリーンアップ dashboard.server_uptime=サーバーの稼働時間 dashboard.current_goroutine=現在のGoroutine数 dashboard.current_memory_usage=現在のメモリ使用量 @@ -2897,9 +2898,15 @@ dashboard.total_gc_time=GC停止時間の合計 dashboard.total_gc_pause=GC停止時間の合計 dashboard.last_gc_pause=前回のGC停止時間 dashboard.gc_times=GC実行回数 +dashboard.delete_old_actions=データベースから古い操作履歴をすべて削除 +dashboard.delete_old_actions.started=データベースからの古い操作履歴の削除を開始しました。 dashboard.update_checker=更新チェック dashboard.delete_old_system_notices=データベースから古いシステム通知をすべて削除 dashboard.gc_lfs=LFSメタオブジェクトのガベージコレクション +dashboard.stop_zombie_tasks=Actionsゾンビタスクを停止 +dashboard.stop_endless_tasks=終わらないActionsタスクを停止 +dashboard.cancel_abandoned_jobs=放置されたままのActionsジョブをキャンセル +dashboard.start_schedule_tasks=Actionsスケジュールタスクを開始 dashboard.sync_branch.started=ブランチの同期を開始しました dashboard.sync_tag.started=タグの同期を開始しました dashboard.rebuild_issue_indexer=イシューインデクサーの再構築 @@ -2974,6 +2981,10 @@ emails.not_updated=メール設定の更新に失敗しました: %v emails.duplicate_active=メールアドレスは別のユーザーが既に使用中です。 emails.change_email_header=メール設定の更新 emails.change_email_text=このメールアドレスで更新してもよろしいですか? +emails.delete=メールアドレスの削除 +emails.delete_desc=このメールアドレスを削除してよろしいですか? +emails.deletion_success=メールアドレスを削除しました。 +emails.delete_primary_email_error=プライマリメールアドレスを削除することはできません。 orgs.org_manage_panel=組織の管理 orgs.name=名称 @@ -3666,6 +3677,7 @@ runs.no_workflows.quick_start=Gitea Actions の始め方がわからない? runs.no_workflows.documentation=Gitea Actions の詳細については、ドキュメントを参照してください。 runs.no_runs=ワークフローはまだ実行されていません。 runs.empty_commit_message=(空のコミットメッセージ) +runs.expire_log_message=ログは古すぎるため消去されています。 workflow.disable=ワークフローを無効にする workflow.disable_success=ワークフロー '%s' が無効になりました。 @@ -3692,6 +3704,7 @@ variables.update.failed=変数を更新できませんでした。 variables.update.success=変数を更新しました。 [projects] +deleted.display_name=削除されたプロジェクト type-1.display_name=個人プロジェクト type-2.display_name=リポジトリ プロジェクト type-3.display_name=組織プロジェクト From a4dac596434df0af7de28d9c1f4630ba32b10515 Mon Sep 17 00:00:00 2001 From: Edip Emre Bodur Date: Tue, 13 Aug 2024 05:53:43 +0300 Subject: [PATCH 14/39] Fixes for unreachable project issues when transfer repository from organization (#31770) When transferring repositories that have issues linked to a project board to another organization, the issues remain associated with the original project board. This causes the columns in the project board to become bugged, making it difficult to move other issues in or out of the affected columns. As a solution, I removed the issue relations since the other organization does not have this project table. Fix for #31538 Co-authored-by: Jason Song --- models/project/issue.go | 6 ++++++ models/project/project.go | 6 ++++++ options/locale/locale_en-US.ini | 1 + services/repository/transfer.go | 17 +++++++++++++++++ templates/repo/settings/options.tmpl | 3 ++- 5 files changed, 32 insertions(+), 1 deletion(-) diff --git a/models/project/issue.go b/models/project/issue.go index 1c31b154ced0..b4347a9c2b4c 100644 --- a/models/project/issue.go +++ b/models/project/issue.go @@ -117,3 +117,9 @@ func (c *Column) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Colum return nil }) } + +// DeleteAllProjectIssueByIssueIDsAndProjectIDs delete all project's issues by issue's and project's ids +func DeleteAllProjectIssueByIssueIDsAndProjectIDs(ctx context.Context, issueIDs, projectIDs []int64) error { + _, err := db.GetEngine(ctx).In("project_id", projectIDs).In("issue_id", issueIDs).Delete(&ProjectIssue{}) + return err +} diff --git a/models/project/project.go b/models/project/project.go index 8cebf34b5ee6..050ccf44e02e 100644 --- a/models/project/project.go +++ b/models/project/project.go @@ -296,6 +296,12 @@ func GetProjectForRepoByID(ctx context.Context, repoID, id int64) (*Project, err return p, nil } +// GetAllProjectsIDsByOwnerID returns the all projects ids it owns +func GetAllProjectsIDsByOwnerIDAndType(ctx context.Context, ownerID int64, projectType Type) ([]int64, error) { + projects := make([]int64, 0) + return projects, db.GetEngine(ctx).Table(&Project{}).Where("owner_id=? AND type=?", ownerID, projectType).Cols("id").Find(&projects) +} + // UpdateProject updates project properties func UpdateProject(ctx context.Context, p *Project) error { if !IsCardTypeValid(p.CardType) { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index b30504edd796..f3a7a3895146 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2186,6 +2186,7 @@ settings.transfer_in_progress = There is currently an ongoing transfer. Please c settings.transfer_notices_1 = - You will lose access to the repository if you transfer it to an individual user. settings.transfer_notices_2 = - You will keep access to the repository if you transfer it to an organization that you (co-)own. settings.transfer_notices_3 = - If the repository is private and is transferred to an individual user, this action makes sure that the user does have at least read permission (and changes permissions if necessary). +settings.transfer_notices_4 = - If the repository belongs to an organization, and you transfer it to another organization or individual, you will lose the links between the repository's issues and the organization's project board. settings.transfer_owner = New Owner settings.transfer_perform = Perform Transfer settings.transfer_started = This repository has been marked for transfer and awaits confirmation from "%s" diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 9e0ff7ae1405..f48653072a81 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" + project_model "code.gitea.io/gitea/models/project" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" @@ -177,6 +178,22 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName } } + // Remove project's issues that belong to old organization's projects + if oldOwner.IsOrganization() { + projects, err := project_model.GetAllProjectsIDsByOwnerIDAndType(ctx, oldOwner.ID, project_model.TypeOrganization) + if err != nil { + return fmt.Errorf("Unable to find old org projects: %w", err) + } + issues, err := issues_model.GetIssueIDsByRepoID(ctx, repo.ID) + if err != nil { + return fmt.Errorf("Unable to find repo's issues: %w", err) + } + err = project_model.DeleteAllProjectIssueByIssueIDsAndProjectIDs(ctx, issues, projects) + if err != nil { + return fmt.Errorf("Unable to delete project's issues: %w", err) + } + } + if newOwner.IsOrganization() { teams, err := organization.FindOrgTeams(ctx, newOwner.ID) if err != nil { diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index f12bbbdf4a7f..8f71f0020f1a 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -957,7 +957,8 @@
{{ctx.Locale.Tr "repo.settings.transfer_notices_1"}}
{{ctx.Locale.Tr "repo.settings.transfer_notices_2"}}
- {{ctx.Locale.Tr "repo.settings.transfer_notices_3"}} + {{ctx.Locale.Tr "repo.settings.transfer_notices_3"}}
+ {{ctx.Locale.Tr "repo.settings.transfer_notices_4"}}
{{.CsrfTokenHtml}} From 7d1348350e2d3b468461455b4e9016fed60974b7 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Wed, 14 Aug 2024 00:28:12 +0000 Subject: [PATCH 15/39] [skip ci] Updated translations via Crowdin --- options/locale/locale_pt-PT.ini | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 5526a00fc302..8cec7c79eadf 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1890,6 +1890,7 @@ pulls.cmd_instruction_checkout_title=Conferir pulls.cmd_instruction_checkout_desc=No seu repositório, irá criar um novo ramo para que possa testar as modificações. pulls.cmd_instruction_merge_title=Integrar pulls.cmd_instruction_merge_desc=Integrar as modificações e enviar para o Gitea. +pulls.cmd_instruction_merge_warning=Aviso: Esta operação não pode executar pedidos de integração porque "auto-identificar integração manual" não estava habilitado pulls.clear_merge_message=Apagar mensagem de integração pulls.clear_merge_message_hint=Apagar a mensagem de integração apenas remove o conteúdo da mensagem de cometimento e mantém os rodapés do git, tais como "Co-Autorado-Por …". @@ -2464,6 +2465,18 @@ settings.thread_id=ID da discussão settings.matrix.homeserver_url=URL do servidor caseiro settings.matrix.room_id=ID da sala settings.matrix.message_type=Tipo de mensagem +settings.visibility.private.button=Tornar privado +settings.visibility.private.text=Mudar a visibilidade para privado não só irá tornar o repositório visível somente para membros autorizados como também poderá remover a relação entre o repositório e derivações, vigilâncias e favoritos. +settings.visibility.private.bullet_title=Mudar a visibilidade para privado irá: +settings.visibility.private.bullet_one=Tornar o repositório visível somente para membros autorizados. +settings.visibility.private.bullet_two=Possivelmente remover a relação entre o repositório e derivações, vigilâncias e favoritos. +settings.visibility.public.button=Tornar público +settings.visibility.public.text=Mudar a visibilidade para público irá tornar o repositório visível para qualquer pessoa. +settings.visibility.public.bullet_title=Mudar a visibilidade para público irá: +settings.visibility.public.bullet_one=Tornar o repositório visível para qualquer pessoa. +settings.visibility.success=A visibilidade do repositório foi modificada. +settings.visibility.error=Ocorreu um erro ao tentar modificar a visibilidade do repositório. +settings.visibility.fork_error=Não é possível modificar a visibilidade de um repositório derivado de outro. settings.archive.button=Arquivar repositório settings.archive.header=Arquivar este repositório settings.archive.text=Arquivar o repositório irá torná-lo apenas de leitura. Ficará escondido do painel de controlo. Ninguém (nem você!) será capaz de fazer novos cometimentos, abrir questões ou pedidos de integração. From 7569a470fb552c8aa0186fcdcc8e8bd921cfd6e0 Mon Sep 17 00:00:00 2001 From: Sebastian Luino Date: Tue, 13 Aug 2024 21:58:26 -0400 Subject: [PATCH 16/39] Add spacing to global error message (#31826) Fixes https://github.com/go-gitea/gitea/issues/31717. Include Typescript files in Tailwind config so they can be pre-processed. ![Screenshot from 2024-08-13 08-44-33](https://github.com/user-attachments/assets/196d7801-e299-4000-8b39-cd9f89917f17) --- tailwind.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tailwind.config.js b/tailwind.config.js index 8f3e8c82517c..fe285432f33d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -36,7 +36,7 @@ export default { '!./modules/{public,options,templates}/bindata.go', './{build,models,modules,routers,services}/**/*.go', './templates/**/*.tmpl', - './web_src/js/**/*.{js,vue}', + './web_src/js/**/*.{ts,js,vue}', ].filter(Boolean), blocklist: [ // classes that don't work without CSS variables from "@tailwind base" which we don't use From 1310649331648d747c57a52ea3bc92da85e7d4d1 Mon Sep 17 00:00:00 2001 From: Rowan Bohde Date: Wed, 14 Aug 2024 16:50:09 -0500 Subject: [PATCH 17/39] render plain text file if the LFS object doesn't exist (#31812) We had an issue where a repo was using LFS to store a file, but the user did not push the file. When trying to view the file, Gitea returned a 500 HTTP status code referencing `ErrLFSObjectNotExist`. It appears the intent was the render this file as plain text, but the conditional was flipped. I've also added a test to verify that the file is rendered as plain text. --- routers/web/repo/view.go | 6 ++---- .../30/77e1c4c8964613df72c37d14275c1eda5228a9 | 2 ++ .../6b/bc79965141058b0026f2064dfb6d2eae3c4540 | Bin 0 -> 259 bytes .../b0/89e97ee59224e8c5676673c096ee4b6a8b9342 | Bin 0 -> 123 bytes .../e9/c32647bab825977942598c0efa415de300304b | Bin 0 -> 170 bytes .../user2/lfs.git/refs/heads/master | 2 +- tests/integration/lfs_view_test.go | 13 +++++++++++++ 7 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 tests/gitea-repositories-meta/user2/lfs.git/objects/30/77e1c4c8964613df72c37d14275c1eda5228a9 create mode 100644 tests/gitea-repositories-meta/user2/lfs.git/objects/6b/bc79965141058b0026f2064dfb6d2eae3c4540 create mode 100644 tests/gitea-repositories-meta/user2/lfs.git/objects/b0/89e97ee59224e8c5676673c096ee4b6a8b9342 create mode 100644 tests/gitea-repositories-meta/user2/lfs.git/objects/e9/c32647bab825977942598c0efa415de300304b diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index dfc33ff36d8d..5e673864578c 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -234,14 +234,12 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, } meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid) - if err != nil && err != git_model.ErrLFSObjectNotExist { // fallback to plain file + if err != nil { // fallback to plain file + log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err) return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil } dataRc.Close() - if err != nil { - return nil, nil, nil, err - } dataRc, err = lfs.ReadMetaObject(pointer) if err != nil { diff --git a/tests/gitea-repositories-meta/user2/lfs.git/objects/30/77e1c4c8964613df72c37d14275c1eda5228a9 b/tests/gitea-repositories-meta/user2/lfs.git/objects/30/77e1c4c8964613df72c37d14275c1eda5228a9 new file mode 100644 index 000000000000..c2dc6e5a4f4c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/lfs.git/objects/30/77e1c4c8964613df72c37d14275c1eda5228a9 @@ -0,0 +1,2 @@ +xKOR00`p t + sMQH)I-I+VHLK3rSS,ݒԊ.-t"U&e23,1'8A \ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/lfs.git/objects/6b/bc79965141058b0026f2064dfb6d2eae3c4540 b/tests/gitea-repositories-meta/user2/lfs.git/objects/6b/bc79965141058b0026f2064dfb6d2eae3c4540 new file mode 100644 index 0000000000000000000000000000000000000000..97455cbc46ed6801ec009d2a6b4cd2ce2347bcfa GIT binary patch literal 259 zcmV+e0sQ`W0V^p=O;s>5GiER}FfcPQQP4}zEJ-XWDauSLElDkAFera`L;rm60 zYem#!&dKpx_e*7yzQUx#SH0EjKjpuUfGQ1g zb#(D{1u1GOyR>L}<@y;qT)(Dg->$R{y5h6~swlarvY2PJph~g|Qq%Ra3ep)$ z6c2+oLUCzQN@fwmo~L{(J~6+WWE+*j%c{_|--yj4 J4*(iHX&$FKd=UTu literal 0 HcmV?d00001 diff --git a/tests/gitea-repositories-meta/user2/lfs.git/objects/b0/89e97ee59224e8c5676673c096ee4b6a8b9342 b/tests/gitea-repositories-meta/user2/lfs.git/objects/b0/89e97ee59224e8c5676673c096ee4b6a8b9342 new file mode 100644 index 0000000000000000000000000000000000000000..33ab64e7303e418c3270708ab1134aa417e2f8a5 GIT binary patch literal 123 zcmV->0EGW|0S(H*5yBu406^bVK?4Ti;0Wo4<3N~+k`c_q>dk9EOM54&jlZ4wGg^Pk zI_EJqrJilx_cE5t`lTiHml{V->eQk)mZL`Fa0{&cO0H=4um~0kgDKW&E&*anhL}S} d@IcvKG2ohQij0J4Jvc2!`(|>;)QDtA}L>CG-fnfu!AS1h!QiCB1%~E97$q2B!9P zZ3D>7A7dARSW2DdIR@)3hExzKDq-}jB{E4CSAFnke)BHdfXP)w$0bS?=fzUd*-A>m zaSci1l$10PoBSBJzQeWu0Z_Lv;< YW^OVwwC$Of#<1uev@K%(0i~r&qPFu Date: Thu, 15 Aug 2024 00:27:03 +0000 Subject: [PATCH 18/39] [skip ci] Updated translations via Crowdin --- options/locale/locale_pt-PT.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 8cec7c79eadf..17a1e82ecc90 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -2185,6 +2185,7 @@ settings.transfer_in_progress=Está a ser feita uma transferência. Cancele-a, p settings.transfer_notices_1=- Você perderá o acesso ao repositório se o transferir para um utilizador individual. settings.transfer_notices_2=- Você manterá o acesso ao repositório se o transferir para uma organização da qual você é (co-)proprietário(a). settings.transfer_notices_3=- Se o repositório for privado e for transferido para um utilizador individual, esta operação certifica que o utilizador tem pelo menos a permissão de leitura (e altera as permissões se for necessário). +settings.transfer_notices_4=- se o repositório pertencer a uma organização e o transferir para outra organização ou indivíduo, irá perder as ligações entre as questões do repositório e o quadro de planeamento da organização. settings.transfer_owner=Novo proprietário settings.transfer_perform=Executar transferência settings.transfer_started=`Este repositório foi marcado para ser transferido e aguarda a confirmação de "%s"` From b491b2104f83ee8fc4956c099c427b339291b3be Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 15 Aug 2024 23:59:01 +0800 Subject: [PATCH 19/39] Fix panic of ssh public key page after deletion of auth source (#31829) Fix #31730 This PR rewrote the function `PublicKeysAreExternallyManaged` with a simple test. The new function removed the loop to make it more readable. --- models/asymkey/ssh_key.go | 23 +++++++---------------- models/asymkey/ssh_key_test.go | 10 ++++++++++ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/models/asymkey/ssh_key.go b/models/asymkey/ssh_key.go index a409d8e84192..7a18732c327a 100644 --- a/models/asymkey/ssh_key.go +++ b/models/asymkey/ssh_key.go @@ -229,35 +229,26 @@ func UpdatePublicKeyUpdated(ctx context.Context, id int64) error { // PublicKeysAreExternallyManaged returns whether the provided KeyID represents an externally managed Key func PublicKeysAreExternallyManaged(ctx context.Context, keys []*PublicKey) ([]bool, error) { - sources := make([]*auth.Source, 0, 5) + sourceCache := make(map[int64]*auth.Source, len(keys)) externals := make([]bool, len(keys)) -keyloop: + for i, key := range keys { if key.LoginSourceID == 0 { externals[i] = false - continue keyloop - } - - var source *auth.Source - - sourceloop: - for _, s := range sources { - if s.ID == key.LoginSourceID { - source = s - break sourceloop - } + continue } - if source == nil { + source, ok := sourceCache[key.LoginSourceID] + if !ok { var err error source, err = auth.GetSourceByID(ctx, key.LoginSourceID) if err != nil { if auth.IsErrSourceNotExist(err) { externals[i] = false - sources[i] = &auth.Source{ + sourceCache[key.LoginSourceID] = &auth.Source{ ID: key.LoginSourceID, } - continue keyloop + continue } return nil, err } diff --git a/models/asymkey/ssh_key_test.go b/models/asymkey/ssh_key_test.go index d3e886b97f95..18c23dc78cb0 100644 --- a/models/asymkey/ssh_key_test.go +++ b/models/asymkey/ssh_key_test.go @@ -12,6 +12,8 @@ import ( "strings" "testing" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/setting" "github.com/42wim/sshsig" @@ -503,3 +505,11 @@ func runErr(t *testing.T, stdin []byte, args ...string) { t.Fatal("expected error") } } + +func Test_PublicKeysAreExternallyManaged(t *testing.T) { + key1 := unittest.AssertExistsAndLoadBean(t, &PublicKey{ID: 1}) + externals, err := PublicKeysAreExternallyManaged(db.DefaultContext, []*PublicKey{key1}) + assert.NoError(t, err) + assert.Len(t, externals, 1) + assert.False(t, externals[0]) +} From 7092402a2db255ecde2c20574b973fb632c16d2e Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Fri, 16 Aug 2024 01:34:24 +0900 Subject: [PATCH 20/39] Add missing repository type filter parameters to pager (#31832) Fix #31807 ps: the newly added params's value will be changed. When the first time you selected the filter, the values of params will be `0` or `1` But in pager it will be `true` or `false`. So do we have `boolToInt` function? --- routers/web/explore/repo.go | 15 +++++++++++++++ routers/web/org/home.go | 16 ++++++++++++++++ routers/web/user/notification.go | 15 +++++++++++++++ routers/web/user/profile.go | 15 +++++++++++++++ 4 files changed, 61 insertions(+) diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go index 67f138aca9ff..ab487dff5fcb 100644 --- a/routers/web/explore/repo.go +++ b/routers/web/explore/repo.go @@ -144,6 +144,21 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { pager.AddParamString("topic", fmt.Sprint(topicOnly)) pager.AddParamString("language", language) pager.AddParamString(relevantReposOnlyParam, fmt.Sprint(opts.OnlyShowRelevant)) + if archived.Has() { + pager.AddParamString("archived", fmt.Sprint(archived.Value())) + } + if fork.Has() { + pager.AddParamString("fork", fmt.Sprint(fork.Value())) + } + if mirror.Has() { + pager.AddParamString("mirror", fmt.Sprint(mirror.Value())) + } + if template.Has() { + pager.AddParamString("template", fmt.Sprint(template.Value())) + } + if private.Has() { + pager.AddParamString("private", fmt.Sprint(private.Value())) + } ctx.Data["Page"] = pager ctx.HTML(http.StatusOK, opts.TplName) diff --git a/routers/web/org/home.go b/routers/web/org/home.go index c3fc4e099a4b..77d49f5b78fd 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -4,6 +4,7 @@ package org import ( + "fmt" "net/http" "path" "strings" @@ -155,6 +156,21 @@ func Home(ctx *context.Context) { pager := context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5) pager.SetDefaultParams(ctx) pager.AddParamString("language", language) + if archived.Has() { + pager.AddParamString("archived", fmt.Sprint(archived.Value())) + } + if fork.Has() { + pager.AddParamString("fork", fmt.Sprint(fork.Value())) + } + if mirror.Has() { + pager.AddParamString("mirror", fmt.Sprint(mirror.Value())) + } + if template.Has() { + pager.AddParamString("template", fmt.Sprint(template.Value())) + } + if private.Has() { + pager.AddParamString("private", fmt.Sprint(private.Value())) + } ctx.Data["Page"] = pager ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0 diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go index 833a9c049c80..414cb0be4907 100644 --- a/routers/web/user/notification.go +++ b/routers/web/user/notification.go @@ -446,6 +446,21 @@ func NotificationWatching(ctx *context.Context) { // redirect to last page if request page is more than total pages pager := context.NewPagination(total, setting.UI.User.RepoPagingNum, page, 5) pager.SetDefaultParams(ctx) + if archived.Has() { + pager.AddParamString("archived", fmt.Sprint(archived.Value())) + } + if fork.Has() { + pager.AddParamString("fork", fmt.Sprint(fork.Value())) + } + if mirror.Has() { + pager.AddParamString("mirror", fmt.Sprint(mirror.Value())) + } + if template.Has() { + pager.AddParamString("template", fmt.Sprint(template.Value())) + } + if private.Has() { + pager.AddParamString("private", fmt.Sprint(private.Value())) + } ctx.Data["Page"] = pager ctx.Data["Status"] = 2 diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index f0749e10216e..3f91233ee6ee 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -333,6 +333,21 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb pager.AddParamString("date", fmt.Sprint(ctx.Data["Date"])) } } + if archived.Has() { + pager.AddParamString("archived", fmt.Sprint(archived.Value())) + } + if fork.Has() { + pager.AddParamString("fork", fmt.Sprint(fork.Value())) + } + if mirror.Has() { + pager.AddParamString("mirror", fmt.Sprint(mirror.Value())) + } + if template.Has() { + pager.AddParamString("template", fmt.Sprint(template.Value())) + } + if private.Has() { + pager.AddParamString("private", fmt.Sprint(private.Value())) + } ctx.Data["Page"] = pager } From e4f850bf635365083344af0c0109c29e7021ba03 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Fri, 16 Aug 2024 00:27:28 +0000 Subject: [PATCH 21/39] [skip ci] Updated translations via Crowdin --- options/locale/locale_ja-JP.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index b36b32043963..4fc06bc71a04 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -1475,6 +1475,7 @@ issues.remove_labels=がラベル %s を除去 %s issues.add_remove_labels=がラベル %s を追加、 %s を除去 %s issues.add_milestone_at=`がマイルストーン %[1]s に追加 %[2]s` issues.add_project_at=`がプロジェクト %s に追加 %s` +issues.move_to_column_of_project=`がこれを %[2]s の %[1]s に移動 %[3]s` issues.change_milestone_at=`がマイルストーンを %[1]s から %[2]s へ変更 %[3]s` issues.change_project_at=`がプロジェクトを %s から %s へ変更 %s` issues.remove_milestone_at=`がマイルストーン %[1]s から除去 %[2]s` @@ -1764,6 +1765,7 @@ compare.compare_head=比較 pulls.desc=プルリクエストとコードレビューの有効化。 pulls.new=新しいプルリクエスト pulls.new.blocked_user=リポジトリのオーナーがあなたをブロックしているため、プルリクエストを作成できません。 +pulls.new.must_collaborator=プルリクエストを作成するには、共同作業者である必要があります。 pulls.edit.already_changed=プルリクエストの変更を保存できません。 他のユーザーによって内容がすでに変更されているようです。 変更を上書きしないようにするため、ページを更新してからもう一度編集してください pulls.view=プルリクエストを表示 pulls.compare_changes=新規プルリクエスト @@ -1888,6 +1890,7 @@ pulls.cmd_instruction_checkout_title=チェックアウト pulls.cmd_instruction_checkout_desc=プロジェクトリポジトリから新しいブランチをチェックアウトし、変更内容をテストします。 pulls.cmd_instruction_merge_title=マージ pulls.cmd_instruction_merge_desc=変更内容をマージして、Giteaに反映します。 +pulls.cmd_instruction_merge_warning=警告: 「手動マージの自動検出」が有効ではないため、この操作ではプルリクエストをマージできません pulls.clear_merge_message=マージメッセージをクリア pulls.clear_merge_message_hint=マージメッセージのクリアは、コミットメッセージの除去だけを行います。 生成されたGitトレーラー("Co-Authored-By …" 等)はそのまま残ります。 From 2010fbe06054b9d9cada430f1a60f45ac7689b93 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Fri, 16 Aug 2024 20:40:51 +0800 Subject: [PATCH 22/39] Fix raw wiki links (#31825) Fix #31395 This regression is introduced by #30273. To find out how GitHub handles this case, I did [some tests](https://github.com/go-gitea/gitea/issues/31395#issuecomment-2278929115). I use redirect in this PR instead of checking if the corresponding `.md` file exists when rendering the link because GitHub also uses redirect. With this PR, there is no need to resolve the raw wiki link when rendering a wiki page. If a wiki link points to a raw file, access will be redirected to the raw link. --- modules/markup/html_link.go | 12 ++--- modules/markup/html_test.go | 2 +- modules/markup/markdown/markdown_test.go | 24 ++++----- routers/web/repo/wiki.go | 64 ++++++++++++++++++++---- routers/web/repo/wiki_test.go | 14 ++++++ 5 files changed, 84 insertions(+), 32 deletions(-) diff --git a/modules/markup/html_link.go b/modules/markup/html_link.go index a41b87e9fa83..b08613534852 100644 --- a/modules/markup/html_link.go +++ b/modules/markup/html_link.go @@ -4,8 +4,6 @@ package markup import ( - "path" - "code.gitea.io/gitea/modules/util" ) @@ -14,13 +12,9 @@ func ResolveLink(ctx *RenderContext, link, userContentAnchorPrefix string) (resu if !isAnchorFragment && !IsFullURLString(link) { linkBase := ctx.Links.Base if ctx.IsWiki { - if ext := path.Ext(link); ext == "" || ext == ".-" { - linkBase = ctx.Links.WikiLink() // the link is for a wiki page - } else if DetectMarkupTypeByFileName(link) != "" { - linkBase = ctx.Links.WikiLink() // the link is renderable as a wiki page - } else { - linkBase = ctx.Links.WikiRawLink() // otherwise, use a raw link instead to view&download medias - } + // no need to check if the link should be resolved as a wiki link or a wiki raw link + // just use wiki link here and it will be redirected to a wiki raw link if necessary + linkBase = ctx.Links.WikiLink() } else if ctx.Links.BranchPath != "" || ctx.Links.TreePath != "" { // if there is no BranchPath, then the link will be something like "/owner/repo/src/{the-file-path}" // and then this link will be handled by the "legacy-ref" code and be redirected to the default branch like "/owner/repo/src/branch/main/{the-file-path}" diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index c69f3ddd648b..32858dbd6b31 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -437,7 +437,7 @@ func TestRender_ShortLinks(t *testing.T) { renderableFileURL := util.URLJoin(tree, "markdown_file.md") renderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "markdown_file.md") unrenderableFileURL := util.URLJoin(tree, "file.zip") - unrenderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "file.zip") + unrenderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "file.zip") favicon := "http://google.com/favicon.ico" test( diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 671276e45c5b..cfb821ab19ff 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -672,9 +672,9 @@ space

Expected: `

space @mention-user
/just/a/path.bin
https://example.com/file.bin
-local link
+local link
remote link
-local link
+local link
remote link
local image
local image
@@ -730,9 +730,9 @@ space

Expected: `

space @mention-user
/just/a/path.bin
https://example.com/file.bin
-local link
+local link
remote link
-local link
+local link
remote link
local image
local image
@@ -788,9 +788,9 @@ space

Expected: `

space @mention-user
/just/a/path.bin
https://example.com/file.bin
-local link
+local link
remote link
-local link
+local link
remote link
local image
local image
@@ -848,9 +848,9 @@ space

Expected: `

space @mention-user
/just/a/path.bin
https://example.com/file.bin
-local link
+local link
remote link
-local link
+local link
remote link
local image
local image
@@ -908,9 +908,9 @@ space

Expected: `

space @mention-user
/just/a/path.bin
https://example.com/file.bin
-local link
+local link
remote link
-local link
+local link
remote link
local image
local image
@@ -970,9 +970,9 @@ space

Expected: `

space @mention-user
/just/a/path.bin
https://example.com/file.bin
-local link
+local link
remote link
-local link
+local link
remote link
local image
local image
diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 13b6a7b8e3b9..d2056353d886 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -138,18 +138,41 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte { return content } -// wikiContentsByName returns the contents of a wiki page, along with a boolean -// indicating whether the page exists. Writes to ctx if an error occurs. -func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) ([]byte, *git.TreeEntry, string, bool) { +// wikiEntryByName returns the entry of a wiki page, along with a boolean +// indicating whether the entry exists. Writes to ctx if an error occurs. +// The last return value indicates whether the file should be returned as a raw file +func wikiEntryByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) (*git.TreeEntry, string, bool, bool) { + isRaw := false gitFilename := wiki_service.WebPathToGitPath(wikiName) entry, err := findEntryForFile(commit, gitFilename) if err != nil && !git.IsErrNotExist(err) { ctx.ServerError("findEntryForFile", err) - return nil, nil, "", false - } else if entry == nil { + return nil, "", false, false + } + if entry == nil { + // check if the file without ".md" suffix exists + gitFilename := strings.TrimSuffix(gitFilename, ".md") + entry, err = findEntryForFile(commit, gitFilename) + if err != nil && !git.IsErrNotExist(err) { + ctx.ServerError("findEntryForFile", err) + return nil, "", false, false + } + isRaw = true + } + if entry == nil { + return nil, "", true, false + } + return entry, gitFilename, false, isRaw +} + +// wikiContentsByName returns the contents of a wiki page, along with a boolean +// indicating whether the page exists. Writes to ctx if an error occurs. +func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) ([]byte, *git.TreeEntry, string, bool) { + entry, gitFilename, noEntry, _ := wikiEntryByName(ctx, commit, wikiName) + if entry == nil { return nil, nil, "", true } - return wikiContentsByEntry(ctx, entry), entry, gitFilename, false + return wikiContentsByEntry(ctx, entry), entry, gitFilename, noEntry } func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { @@ -215,11 +238,14 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { isSideBar := pageName == "_Sidebar" isFooter := pageName == "_Footer" - // lookup filename in wiki - get filecontent, gitTree entry , real filename - data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName) + // lookup filename in wiki - get gitTree entry , real filename + entry, pageFilename, noEntry, isRaw := wikiEntryByName(ctx, commit, pageName) if noEntry { ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages") } + if isRaw { + ctx.Redirect(util.URLJoin(ctx.Repo.RepoLink, "wiki/raw", string(pageName))) + } if entry == nil || ctx.Written() { if wikiRepo != nil { wikiRepo.Close() @@ -227,6 +253,15 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { return nil, nil } + // get filecontent + data := wikiContentsByEntry(ctx, entry) + if ctx.Written() { + if wikiRepo != nil { + wikiRepo.Close() + } + return nil, nil + } + var sidebarContent []byte if !isSideBar { sidebarContent, _, _, _ = wikiContentsByName(ctx, commit, "_Sidebar") @@ -442,15 +477,24 @@ func renderEditPage(ctx *context.Context) { ctx.Data["Title"] = displayName ctx.Data["title"] = displayName - // lookup filename in wiki - get filecontent, gitTree entry , real filename - data, entry, _, noEntry := wikiContentsByName(ctx, commit, pageName) + // lookup filename in wiki - gitTree entry , real filename + entry, _, noEntry, isRaw := wikiEntryByName(ctx, commit, pageName) if noEntry { ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages") } + if isRaw { + ctx.Error(http.StatusForbidden, "Editing of raw wiki files is not allowed") + } if entry == nil || ctx.Written() { return } + // get filecontent + data := wikiContentsByEntry(ctx, entry) + if ctx.Written() { + return + } + ctx.Data["content"] = string(data) ctx.Data["sidebarPresent"] = false ctx.Data["sidebarContent"] = "" diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go index 7de5899e21c1..b81f2ea02e32 100644 --- a/routers/web/repo/wiki_test.go +++ b/routers/web/repo/wiki_test.go @@ -87,6 +87,13 @@ func TestWiki(t *testing.T) { assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) assert.EqualValues(t, "Home", ctx.Data["Title"]) assertPagesMetas(t, []string{"Home", "Page With Image", "Page With Spaced Name", "Unescaped File"}, ctx.Data["Pages"]) + + ctx, _ = contexttest.MockContext(t, "user2/repo1/jpeg.jpg") + ctx.SetPathParam("*", "jpeg.jpg") + contexttest.LoadRepo(t, ctx, 1) + Wiki(ctx) + assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status()) + assert.Equal(t, "/user2/repo1/wiki/raw/jpeg.jpg", ctx.Resp.Header().Get("Location")) } func TestWikiPages(t *testing.T) { @@ -160,6 +167,13 @@ func TestEditWiki(t *testing.T) { assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) assert.EqualValues(t, "Home", ctx.Data["Title"]) assert.Equal(t, wikiContent(t, ctx.Repo.Repository, "Home"), ctx.Data["content"]) + + ctx, _ = contexttest.MockContext(t, "user2/repo1/wiki/jpeg.jpg?action=_edit") + ctx.SetPathParam("*", "jpeg.jpg") + contexttest.LoadUser(t, ctx, 2) + contexttest.LoadRepo(t, ctx, 1) + EditWiki(ctx) + assert.EqualValues(t, http.StatusForbidden, ctx.Resp.Status()) } func TestEditWikiPost(t *testing.T) { From acd7053e9d4968e8b9812ab379be9027ac8e7771 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Sat, 17 Aug 2024 01:04:54 +0800 Subject: [PATCH 23/39] Avoid returning without written ctx when posting PR (#31843) Fix #31625. If `pull_service.NewPullRequest` return an error which misses each `if` check, `CompareAndPullRequestPost` will return immediately, since it doesn't write the HTTP response, a 200 response with empty body will be sent to clients. ```go if err := pull_service.NewPullRequest(ctx, repo, pullIssue, labelIDs, attachments, pullRequest, assigneeIDs); err != nil { if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) { ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error()) } else if git.IsErrPushRejected(err) { // ... ctx.JSONError(flashError) } else if errors.Is(err, user_model.ErrBlockedUser) { // ... ctx.JSONError(flashError) } else if errors.Is(err, issues_model.ErrMustCollaborator) { // ... ctx.JSONError(flashError) } return } ``` Not sure what kind of error can cause it to happen, so this PR just expose it. And we can fix it when users report that creating PRs failed with error responses. It's all my guess since I cannot reproduce the problem, but even if it's not related, the code here needs to be improved. --- routers/web/repo/pull.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 9531482bee76..e001e872aa8a 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1308,9 +1308,10 @@ func CompareAndPullRequestPost(ctx *context.Context) { // instead of 500. if err := pull_service.NewPullRequest(ctx, repo, pullIssue, labelIDs, attachments, pullRequest, assigneeIDs); err != nil { - if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) { + switch { + case repo_model.IsErrUserDoesNotHaveAccessToRepo(err): ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error()) - } else if git.IsErrPushRejected(err) { + case git.IsErrPushRejected(err): pushrejErr := err.(*git.ErrPushRejected) message := pushrejErr.Message if len(message) == 0 { @@ -1327,7 +1328,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { return } ctx.JSONError(flashError) - } else if errors.Is(err, user_model.ErrBlockedUser) { + case errors.Is(err, user_model.ErrBlockedUser): flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.pulls.push_rejected"), "Summary": ctx.Tr("repo.pulls.new.blocked_user"), @@ -1337,7 +1338,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { return } ctx.JSONError(flashError) - } else if errors.Is(err, issues_model.ErrMustCollaborator) { + case errors.Is(err, issues_model.ErrMustCollaborator): flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.pulls.push_rejected"), "Summary": ctx.Tr("repo.pulls.new.must_collaborator"), @@ -1347,6 +1348,11 @@ func CompareAndPullRequestPost(ctx *context.Context) { return } ctx.JSONError(flashError) + default: + // It's an unexpected error. + // If it happens, we should add another case to handle it. + log.Error("Unexpected error of NewPullRequest: %T %s", err, err) + ctx.ServerError("CompareAndPullRequest", err) } return } From 2e7d202a7f8051d140220d1c195ff73b80e0d1ff Mon Sep 17 00:00:00 2001 From: sillyguodong <33891828+sillyguodong@users.noreply.github.com> Date: Sat, 17 Aug 2024 01:37:36 +0800 Subject: [PATCH 24/39] fix the component of access token list not mounted (#31824) try to fix #31771 --- templates/user/settings/applications.tmpl | 14 ++++++------- .../components/ScopedAccessTokenSelector.vue | 13 ------------ web_src/js/features/scoped-access-token.ts | 20 +++++++++++++++++++ web_src/js/index.ts | 2 +- 4 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 web_src/js/features/scoped-access-token.ts diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index 3c1934dd8b47..65525aac2b21 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -77,13 +77,13 @@

{{ctx.Locale.Tr "settings.access_token_desc" (HTMLFormat `href="%s/api/swagger" target="_blank"` AppSubUrl) (`href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`|SafeHTML)}}

-
- +