fix: correct full url when using sub-path (#38712)

fix #38708

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
wxiaoguang
2026-07-31 03:09:32 +08:00
committed by GitHub
parent d87d26d735
commit a30d865b78
12 changed files with 19 additions and 44 deletions
+1 -1
View File
@@ -78,7 +78,7 @@ func (c TemplateContext) CurrentWebBanner() *setting.WebBannerType {
return nil
}
// AppFullLink returns a full URL link with AppSubURL for the given app link (no AppSubURL)
// AppFullLink returns a full URL link with AppSubURL for the given app link
// If no link is given, it returns the current app full URL with sub-path but without trailing slash (that's why it is not named as AppURL)
func (c TemplateContext) AppFullLink(link ...string) template.URL {
s := httplib.GuessCurrentAppURL(c.parentContext())
+1 -1
View File
@@ -79,7 +79,7 @@
{{/*FIXME: the "HasSourceRenderedToggle" is never set on blame page, it should mean "whether the file is renderable".
If the file is renderable, then it must has the "display=source" parameter to make sure the file view page shows the source code, then line number works. */}}
{{if $.Permission.CanRead ctx.Consts.RepoUnitTypeIssues}}
<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>
<a class="item ref-in-new-issue" role="menuitem" data-url-issue-new="{{.RepoLink}}/issues/new" data-url-param-body-link="{{.Repository.HTMLURL ctx}}/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>
@@ -3,13 +3,14 @@
{{svg "octicon-kebab-horizontal"}}
</a>
<div class="menu">
{{$referenceLink := ""}}
{{if .issue}}
{{$referenceLink = printf "%s#%s" ctx.RootData.Issue.Link .item.HashTag}}
{{$issueHTMLURL := ctx.RootData.Issue.HTMLURL ctx}}
{{$referenceHTMLURL := ""}}
{{if .issue}}{{/* boolean value: is issue */}}
{{$referenceHTMLURL = printf "%s#%s" $issueHTMLURL .item.HashTag}}
{{else}}
{{$referenceLink = printf "%s/files#%s" ctx.RootData.Issue.Link .item.HashTag}}
{{$referenceHTMLURL = printf "%s/files#%s" $issueHTMLURL .item.HashTag}}
{{end}}
<div class="item context js-aria-clickable" data-clipboard-text="{{ctx.AppFullLink}}{{$referenceLink}}">{{ctx.Locale.Tr "repo.issues.context.copy_link"}}</div>
<div class="item context js-aria-clickable" data-clipboard-text="{{$referenceHTMLURL}}">{{ctx.Locale.Tr "repo.issues.context.copy_link"}}</div>
<div class="item context js-aria-clickable" data-clipboard-target="#{{.item.HashTag}}-raw">{{ctx.Locale.Tr "repo.issues.context.copy_source"}}</div>
{{if ctx.RootData.IsSigned}}
{{$needDivider := false}}
@@ -17,7 +18,7 @@
{{$needDivider = true}}
<div class="item context js-aria-clickable quote-reply {{if .diff}}quote-reply-diff{{end}}" data-target="{{.item.HashTag}}-raw">{{ctx.Locale.Tr "repo.issues.context.quote_reply"}}</div>
{{if not ctx.Consts.RepoUnitTypeIssues.UnitGlobalDisabled}}
<div class="item context js-aria-clickable reference-issue" data-target="{{.item.HashTag}}-raw" data-modal="#reference-issue-modal" data-poster="{{.item.Poster.GetDisplayName}}" data-poster-username="{{.item.Poster.Name}}" data-reference="{{$referenceLink}}">{{ctx.Locale.Tr "repo.issues.context.reference_issue"}}</div>
<div class="item context js-aria-clickable reference-issue" data-target="{{.item.HashTag}}-raw" data-modal="#reference-issue-modal" data-poster="{{.item.Poster.GetDisplayName}}" data-poster-username="{{.item.Poster.Name}}" data-reference="{{$referenceHTMLURL}}">{{ctx.Locale.Tr "repo.issues.context.reference_issue"}}</div>
{{end}}
{{if or ctx.RootData.Permission.IsAdmin .IsCommentPoster ctx.RootData.HasIssuesOrPullsWritePermission}}
<div class="divider"></div>
@@ -16,7 +16,7 @@
<div class="ui secondary segment tw-font-mono">
{{$gitRemoteName := ctx.RootData.SystemConfig.Repository.GitGuideRemoteName.Value ctx}}
{{if eq $pull.Flow 0}}
<div>git fetch -u {{if ne $pull.HeadRepo.ID $pull.BaseRepo.ID}}{{ctx.AppFullLink $pull.HeadRepo.Link}}{{else}}{{$gitRemoteName}}{{end}} {{$pull.HeadBranch}}:{{$localBranch}}</div>
<div>git fetch -u {{if ne $pull.HeadRepo.ID $pull.BaseRepo.ID}}{{$pull.HeadRepo.HTMLURL ctx}}{{else}}{{$gitRemoteName}}{{end}} {{$pull.HeadBranch}}:{{$localBranch}}</div>
{{else}}
<div>git fetch -u {{$gitRemoteName}} {{$pull.GetGitHeadRefName}}:{{$localBranch}}</div>
{{end}}
+1 -1
View File
@@ -92,7 +92,7 @@
<button class="ui dropdown basic compact jump button tw-px-3" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}">
{{svg "octicon-kebab-horizontal"}}
<div class="menu">
<a class="item" data-clipboard-text="{{ctx.AppFullLink}}{{.Repository.Link}}/src/commit/{{.CommitID}}/{{PathEscapeSegments .TreePath}}">
<a class="item" data-clipboard-text="{{.Repository.HTMLURL ctx}}/src/commit/{{.CommitID}}/{{PathEscapeSegments .TreePath}}">
{{svg "octicon-link" 16}}{{ctx.Locale.Tr "repo.file_copy_permalink"}}
</a>
{{if and (not $.DisableDownloadSourceArchives) $.RefFullName}}
+1 -1
View File
@@ -142,7 +142,7 @@
<div class="code-line-menu tippy-target">
{{if $.Permission.CanRead ctx.Consts.RepoUnitTypeIssues}}
<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>
<a class="item ref-in-new-issue" role="menuitem" data-url-issue-new="{{.RepoLink}}/issues/new" data-url-param-body-link="{{.Repository.HTMLURL ctx}}/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 view_git_blame" role="menuitem" href="{{.Repository.Link}}/blame/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}">{{ctx.Locale.Tr "repo.view_git_blame"}}</a>
<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>
+2 -3
View File
@@ -744,10 +744,9 @@ func TestIssueReferenceURL(t *testing.T) {
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
// the "reference" uses relative URLs, then JS code will convert them to absolute URLs for current origin, in case users are using multiple domains
ref, _ := htmlDoc.Find(`.timeline-item.comment.issue-content-comment .reference-issue`).Attr("data-reference")
assert.Equal(t, "/user2/repo1/issues/1#issue-1", ref)
assert.Equal(t, setting.AppURL+"user2/repo1/issues/1#issue-1", ref)
ref, _ = htmlDoc.Find(`.timeline-item.comment:not(.issue-content-comment) .reference-issue`).Attr("data-reference")
assert.Equal(t, "/user2/repo1/issues/1#issuecomment-2", ref)
assert.Equal(t, setting.AppURL+"user2/repo1/issues/1#issuecomment-2", ref)
}
+2 -2
View File
@@ -580,7 +580,7 @@ func testGeneratedSourceLink(t *testing.T) {
dataURL, exists = doc.doc.Find(".ref-in-new-issue").Attr("data-url-param-body-link")
assert.True(t, exists)
assert.Equal(t, "/user2/repo1/src/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md?display=source", dataURL)
assert.Equal(t, setting.AppURL+"user2/repo1/src/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md?display=source", dataURL)
})
t.Run("Non-Rendered file", func(t *testing.T) {
@@ -597,7 +597,7 @@ func testGeneratedSourceLink(t *testing.T) {
dataURL, exists = doc.doc.Find(".ref-in-new-issue").Attr("data-url-param-body-link")
assert.True(t, exists)
assert.Equal(t, "/user27/repo49/src/commit/aacbdfe9e1c4b47f60abe81849045fa4e96f1d75/test/test.txt", dataURL)
assert.Equal(t, setting.AppURL+"user27/repo49/src/commit/aacbdfe9e1c4b47f60abe81849045fa4e96f1d75/test/test.txt", dataURL)
})
}
+1 -2
View File
@@ -1,6 +1,5 @@
import {svg} from '../svg.ts';
import {createTippy} from '../modules/tippy.ts';
import {toAbsoluteUrl} from '../utils.ts';
import {addDelegatedEventListener} from '../utils/dom.ts';
function changeHash(hash: string) {
@@ -24,7 +23,7 @@ function selectRange(range: string): Element | null {
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
const issueContent = `${urlParamBodyLink}#${anchor}`; // the default content for issue body
refInNewIssue.setAttribute('href', `${urlIssueNew}?body=${encodeURIComponent(issueContent)}`);
};
+1 -2
View File
@@ -11,7 +11,6 @@ import {
} from '../utils/dom.ts';
import {setFileFolding} from './file-fold.ts';
import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
import {toAbsoluteUrl} from '../utils.ts';
import {GET, POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {initRepoIssueSidebar} from './repo-issue-sidebar.ts';
@@ -329,7 +328,7 @@ export function initRepoIssueReferenceIssue() {
const target = el.getAttribute('data-target');
const content = document.querySelector(`#${target}`)?.textContent ?? '';
const poster = el.getAttribute('data-poster-username');
const reference = toAbsoluteUrl(el.getAttribute('data-reference')!);
const reference = el.getAttribute('data-reference')!;
const modalSelector = el.getAttribute('data-modal')!;
const modal = document.querySelector(modalSelector)!;
const textarea = modal.querySelector<HTMLTextAreaElement>('textarea[name="content"]')!;
+1 -11
View File
@@ -1,7 +1,7 @@
import {
dirname, basename, extname, formatBytes, isObject, stripTags, parseIssueHref,
translateMonth, translateDay, blobToDataURI,
toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64, isImageFile, isVideoFile, parseRepoOwnerPathInfo,
encodeURLEncodedBase64, decodeURLEncodedBase64, isImageFile, isVideoFile, parseRepoOwnerPathInfo,
} from './utils.ts';
test('dirname', () => {
@@ -88,16 +88,6 @@ test('blobToDataURI', async () => {
expect(await blobToDataURI(blob)).toEqual('data:application/json;base64,eyJ0ZXN0Ijp0cnVlfQ==');
});
test('toAbsoluteUrl', () => {
expect(toAbsoluteUrl('//host/dir')).toEqual('http://host/dir');
expect(toAbsoluteUrl('https://host/dir')).toEqual('https://host/dir');
expect(toAbsoluteUrl('')).toEqual('http://localhost:3000');
expect(toAbsoluteUrl('/user/repo')).toEqual('http://localhost:3000/user/repo');
expect(() => toAbsoluteUrl('path')).toThrow('unsupported');
});
test('encodeURLEncodedBase64, decodeURLEncodedBase64', () => {
const encoder = new TextEncoder();
const uint8array = encoder.encode.bind(encoder);
-13
View File
@@ -142,19 +142,6 @@ export function convertImage(blob: Blob, mime: string): Promise<Blob> {
});
}
export function toAbsoluteUrl(url: string): string {
if (url.startsWith('http://') || url.startsWith('https://')) {
return url;
}
if (url.startsWith('//')) {
return `${window.location.protocol}${url}`; // it's also a somewhat absolute URL (with the current scheme)
}
if (url && !url.startsWith('/')) {
throw new Error('unsupported url, it should either start with / or http(s)://');
}
return `${window.location.origin}${url}`;
}
/** Encode an Uint8Array into a URLEncoded base64 string. */
export function encodeURLEncodedBase64(uint8Array: Uint8Array): string {
return btoa(Array.from(uint8Array, (byte) => String.fromCharCode(byte)).join(''))