1
0
mirror of https://github.com/go-gitea/gitea synced 2024-11-07 09:15:53 +01:00

Append (comment) when a link points at a comment rather than the whole issue (#23734)

Close #23671 

For the feature mentioned above, this PR append ' (comment)' to the
rendered html if it is a hashcomment.
After the PR, type in the following

```
pull request from other repo:
http://localhost:3000/testOrg/testOrgRepo/pulls/2

pull request from this repo:
http://localhost:3000/aaa/testA/pulls/2

issue comment from this repo:
http://localhost:3000/aaa/testA/issues/1#issuecomment-18

http://localhost:3000/aaa/testA/pulls/2#issue-9

issue comment from other repo:
http://localhost:3000/testOrg/testOrgRepo/pulls/2#issuecomment-24

http://localhost:3000/testOrg/testOrgRepo/pulls/2#issue
```
Gives:
<img width="687" alt="截屏2023-03-27 13 53 06"
src="https://user-images.githubusercontent.com/17645053/227852387-2b218e0d-3468-4d90-ad81-d702ddd17fd2.png">



Other than the above feature, this PR also includes two other changes:

1 Right now, the render of links from file changed tab in pull request
might not be very proper, for example, if type in the following. (not
sure if this is an issue or design, if not an issue, I will revert the
changes). example on
[try.gitea.io](https://try.gitea.io/HesterG/testrepo/pulls/1)
```
https://try.gitea.io/HesterG/testrepo/pulls/1/files#issuecomment-162725
https://try.gitea.io/HesterG/testrepo/pulls/1/files
```
it will render the following 
<img width="899" alt="截屏2023-03-24 15 41 37"
src="https://user-images.githubusercontent.com/17645053/227456117-5eccedb7-9118-4540-929d-aee9a76de852.png">

In this PR, skip processing the link into a ref issue if it is a link
from files changed tab in pull request

After:
type in following
```
hash comment on files changed tab:
http://localhost:3000/testOrg/testOrgRepo/pulls/2/files#issuecomment-24

files changed link:
http://localhost:3000/testOrg/testOrgRepo/pulls/2/files
```
Gives

<img width="708" alt="截屏2023-03-27 22 09 02"
src="https://user-images.githubusercontent.com/17645053/227964273-5dc06c50-3713-489c-b05d-d95367d0ab0f.png">

2 Right now, after editing the comment area, there will not be tippys
attached to `ref-issue`; and no tippy attached on preview as well.
example:

https://user-images.githubusercontent.com/17645053/227850540-5ae34e2d-b1d7-4d0d-9726-7701bf825d1f.mov

In this PR, in frontend, make sure tippy is added after editing the
comment, and to the comment on preview tab
After:

https://user-images.githubusercontent.com/17645053/227853777-06f56b4c-1148-467c-b6f7-f79418e67504.mov
This commit is contained in:
Hester Gong 2023-04-03 16:02:57 +08:00 committed by GitHub
parent 977ef215fa
commit ca905b82df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 61 additions and 11 deletions

@ -628,7 +628,9 @@ func (ctx *Context) Value(key interface{}) interface{} {
if key == git.RepositoryContextKey && ctx.Repo != nil { if key == git.RepositoryContextKey && ctx.Repo != nil {
return ctx.Repo.GitRepo return ctx.Repo.GitRepo
} }
if key == translation.ContextKey && ctx.Locale != nil {
return ctx.Locale
}
return ctx.Req.Context().Value(key) return ctx.Req.Context().Value(key)
} }

@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/regexplru" "code.gitea.io/gitea/modules/regexplru"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates/vars" "code.gitea.io/gitea/modules/templates/vars"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"golang.org/x/net/html" "golang.org/x/net/html"
@ -97,14 +98,30 @@ var issueFullPattern *regexp.Regexp
// Once for to prevent races // Once for to prevent races
var issueFullPatternOnce sync.Once var issueFullPatternOnce sync.Once
// regexp for full links to hash comment in pull request files changed tab
var filesChangedFullPattern *regexp.Regexp
// Once for to prevent races
var filesChangedFullPatternOnce sync.Once
func getIssueFullPattern() *regexp.Regexp { func getIssueFullPattern() *regexp.Regexp {
issueFullPatternOnce.Do(func() { issueFullPatternOnce.Do(func() {
// example: https://domain/org/repo/pulls/27#hash
issueFullPattern = regexp.MustCompile(regexp.QuoteMeta(setting.AppURL) + issueFullPattern = regexp.MustCompile(regexp.QuoteMeta(setting.AppURL) +
`[\w_.-]+/[\w_.-]+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b`) `[\w_.-]+/[\w_.-]+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b`)
}) })
return issueFullPattern return issueFullPattern
} }
func getFilesChangedFullPattern() *regexp.Regexp {
filesChangedFullPatternOnce.Do(func() {
// example: https://domain/org/repo/pulls/27/files#hash
filesChangedFullPattern = regexp.MustCompile(regexp.QuoteMeta(setting.AppURL) +
`[\w_.-]+/[\w_.-]+/pulls/((?:\w{1,10}-)?[1-9][0-9]*)/files([\?|#](\S+)?)?\b`)
})
return filesChangedFullPattern
}
// CustomLinkURLSchemes allows for additional schemes to be detected when parsing links within text // CustomLinkURLSchemes allows for additional schemes to be detected when parsing links within text
func CustomLinkURLSchemes(schemes []string) { func CustomLinkURLSchemes(schemes []string) {
schemes = append(schemes, "http", "https") schemes = append(schemes, "http", "https")
@ -793,15 +810,30 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.Metas == nil { if ctx.Metas == nil {
return return
} }
next := node.NextSibling next := node.NextSibling
for node != nil && node != next { for node != nil && node != next {
m := getIssueFullPattern().FindStringSubmatchIndex(node.Data) m := getIssueFullPattern().FindStringSubmatchIndex(node.Data)
if m == nil { if m == nil {
return return
} }
mDiffView := getFilesChangedFullPattern().FindStringSubmatchIndex(node.Data)
// leave it as it is if the link is from "Files Changed" tab in PR Diff View https://domain/org/repo/pulls/27/files
if mDiffView != nil {
return
}
link := node.Data[m[0]:m[1]] link := node.Data[m[0]:m[1]]
id := "#" + node.Data[m[2]:m[3]] text := "#" + node.Data[m[2]:m[3]]
// if m[4] and m[5] is not -1, then link is to a comment
// indicate that in the text by appending (comment)
if m[4] != -1 && m[5] != -1 {
if locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale); ok {
text += " " + locale.Tr("repo.from_comment")
} else {
text += " (comment)"
}
}
// extract repo and org name from matched link like // extract repo and org name from matched link like
// http://localhost:3000/gituser/myrepo/issues/1 // http://localhost:3000/gituser/myrepo/issues/1
@ -810,12 +842,10 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
matchRepo := linkParts[len(linkParts)-3] matchRepo := linkParts[len(linkParts)-3]
if matchOrg == ctx.Metas["user"] && matchRepo == ctx.Metas["repo"] { if matchOrg == ctx.Metas["user"] && matchRepo == ctx.Metas["repo"] {
// TODO if m[4]:m[5] is not nil, then link is to a comment, replaceContent(node, m[0], m[1], createLink(link, text, "ref-issue"))
// and we should indicate that in the text somehow
replaceContent(node, m[0], m[1], createLink(link, id, "ref-issue"))
} else { } else {
orgRepoID := matchOrg + "/" + matchRepo + id text = matchOrg + "/" + matchRepo + text
replaceContent(node, m[0], m[1], createLink(link, orgRepoID, "ref-issue")) replaceContent(node, m[0], m[1], createLink(link, text, "ref-issue"))
} }
node = node.NextSibling.NextSibling node = node.NextSibling.NextSibling
} }

@ -330,13 +330,17 @@ func TestRender_FullIssueURLs(t *testing.T) {
test("Look here http://localhost:3000/person/repo/issues/4", test("Look here http://localhost:3000/person/repo/issues/4",
`Look here <a href="http://localhost:3000/person/repo/issues/4" class="ref-issue">person/repo#4</a>`) `Look here <a href="http://localhost:3000/person/repo/issues/4" class="ref-issue">person/repo#4</a>`)
test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", test("http://localhost:3000/person/repo/issues/4#issuecomment-1234",
`<a href="http://localhost:3000/person/repo/issues/4#issuecomment-1234" class="ref-issue">person/repo#4</a>`) `<a href="http://localhost:3000/person/repo/issues/4#issuecomment-1234" class="ref-issue">person/repo#4 (comment)</a>`)
test("http://localhost:3000/gogits/gogs/issues/4", test("http://localhost:3000/gogits/gogs/issues/4",
`<a href="http://localhost:3000/gogits/gogs/issues/4" class="ref-issue">#4</a>`) `<a href="http://localhost:3000/gogits/gogs/issues/4" class="ref-issue">#4</a>`)
test("http://localhost:3000/gogits/gogs/issues/4 test", test("http://localhost:3000/gogits/gogs/issues/4 test",
`<a href="http://localhost:3000/gogits/gogs/issues/4" class="ref-issue">#4</a> test`) `<a href="http://localhost:3000/gogits/gogs/issues/4" class="ref-issue">#4</a> test`)
test("http://localhost:3000/gogits/gogs/issues/4?a=1&b=2#comment-123 test", test("http://localhost:3000/gogits/gogs/issues/4?a=1&b=2#comment-123 test",
`<a href="http://localhost:3000/gogits/gogs/issues/4?a=1&amp;b=2#comment-123" class="ref-issue">#4</a> test`) `<a href="http://localhost:3000/gogits/gogs/issues/4?a=1&amp;b=2#comment-123" class="ref-issue">#4 (comment)</a> test`)
test("http://localhost:3000/testOrg/testOrgRepo/pulls/2/files#issuecomment-24",
"http://localhost:3000/testOrg/testOrgRepo/pulls/2/files#issuecomment-24")
test("http://localhost:3000/testOrg/testOrgRepo/pulls/2/files",
"http://localhost:3000/testOrg/testOrgRepo/pulls/2/files")
} }
func TestRegExp_sha1CurrentPattern(t *testing.T) { func TestRegExp_sha1CurrentPattern(t *testing.T) {

@ -18,6 +18,10 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
) )
type contextKey struct{}
var ContextKey interface{} = &contextKey{}
// Locale represents an interface to translation // Locale represents an interface to translation
type Locale interface { type Locale interface {
Language() string Language() string

@ -1104,6 +1104,7 @@ download_file = Download file
normal_view = Normal View normal_view = Normal View
line = line line = line
lines = lines lines = lines
from_comment = (comment)
editor.add_file = Add File editor.add_file = Add File
editor.new_file = New File editor.new_file = New File

@ -1,5 +1,6 @@
import $ from 'jquery'; import $ from 'jquery';
import {initMarkupContent} from '../../markup/content.js'; import {initMarkupContent} from '../../markup/content.js';
import {attachRefIssueContextPopup} from '../contextpopup.js';
const {csrfToken} = window.config; const {csrfToken} = window.config;
@ -16,6 +17,8 @@ export function initCompMarkupContentPreviewTab($form) {
}, (data) => { }, (data) => {
const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`); const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`);
$previewPanel.html(data); $previewPanel.html(data);
const refIssues = $previewPanel.find('p .ref-issue');
attachRefIssueContextPopup(refIssues);
initMarkupContent(); initMarkupContent();
}); });
}); });

@ -6,8 +6,11 @@ import {createTippy} from '../modules/tippy.js';
export function initContextPopups() { export function initContextPopups() {
const refIssues = $('.ref-issue'); const refIssues = $('.ref-issue');
if (!refIssues.length) return; attachRefIssueContextPopup(refIssues);
}
export function attachRefIssueContextPopup(refIssues) {
if (!refIssues.length) return;
refIssues.each(function () { refIssues.each(function () {
if ($(this).hasClass('ref-external-issue')) { if ($(this).hasClass('ref-external-issue')) {
return; return;

@ -26,6 +26,7 @@ import {initCompReactionSelector} from './comp/ReactionSelector.js';
import {initRepoSettingBranches} from './repo-settings.js'; import {initRepoSettingBranches} from './repo-settings.js';
import {initRepoPullRequestMergeForm} from './repo-issue-pr-form.js'; import {initRepoPullRequestMergeForm} from './repo-issue-pr-form.js';
import {hideElem, showElem} from '../utils/dom.js'; import {hideElem, showElem} from '../utils/dom.js';
import {attachRefIssueContextPopup} from './contextpopup.js';
const {csrfToken} = window.config; const {csrfToken} = window.config;
@ -439,6 +440,8 @@ async function onEditContent(event) {
} else { } else {
$renderContent.html(data.content); $renderContent.html(data.content);
$rawContent.text($textarea.val()); $rawContent.text($textarea.val());
const refIssues = $renderContent.find('p .ref-issue');
attachRefIssueContextPopup(refIssues);
} }
const $content = $segment; const $content = $segment;
if (!$content.find('.dropzone-attachments').length) { if (!$content.find('.dropzone-attachments').length) {