Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve "goto issue by number" button #24577

Merged
merged 10 commits into from
May 10, 2023
8 changes: 4 additions & 4 deletions templates/repo/issue/search.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
<input type="hidden" name="assignee" value="{{$.AssigneeID}}">
<input type="hidden" name="poster" value="{{$.PosterID}}">
<input name="q" value="{{.Keyword}}" placeholder="{{.locale.Tr "explore.search"}}...">
<button id="hashtag-button" class="ui small icon button gt-hidden" data-tooltip-content="{{.locale.Tr "explore.go_to"}}">{{svg "octicon-hash"}}</button>
<button id="search-button" class="ui small icon button" aria-label="{{.locale.Tr "explore.search"}}">
{{svg "octicon-search"}}
</button>
{{if .PageIsIssueList}}
<button id="issue-list-quick-goto" class="ui small icon button gt-hidden" data-tooltip-content="{{.locale.Tr "explore.go_to"}}" data-repo-link="{{.RepoLink}}">{{svg "octicon-hash"}}</button>
{{end}}
<button class="ui small icon button" aria-label="{{.locale.Tr "explore.search"}}">{{svg "octicon-search"}}</button>
</div>
</form>
4 changes: 2 additions & 2 deletions templates/user/dashboard/issues.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@
<input type="hidden" name="sort" value="{{$.SortType}}">
<input type="hidden" name="state" value="{{$.State}}">
<input name="q" value="{{$.Keyword}}" placeholder="{{.locale.Tr "explore.search"}}...">
<button id="hashtag-button" class="ui small icon button gt-hidden" data-tooltip-content="{{.locale.Tr "explore.go_to"}}">{{svg "octicon-hash"}}</button>
<button id="search-button" class="ui small icon button" aria-label="{{.locale.Tr "explore.search"}}">{{svg "octicon-search"}}</button>
<button id="issue-list-quick-goto" class="ui small icon button gt-hidden" data-tooltip-content="{{.locale.Tr "explore.go_to"}}">{{svg "octicon-hash"}}</button>
<button class="ui small icon button" aria-label="{{.locale.Tr "explore.search"}}">{{svg "octicon-search"}}</button>
</div>
</form>
<!-- Sort -->
Expand Down
4 changes: 2 additions & 2 deletions web_src/js/features/codeeditor.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {basename, extname, isObject, isDarkTheme} from '../utils.js';
import {debounce} from 'throttle-debounce';
import {onInputDebounce} from '../utils/dom.js';

const languagesByFilename = {};
const languagesByExt = {};
Expand Down Expand Up @@ -164,7 +164,7 @@ export async function createCodeEditor(textarea, filenameInput) {
...getEditorConfigOptions(editorConfig),
});

filenameInput.addEventListener('input', debounce(500, () => {
filenameInput.addEventListener('input', onInputDebounce(() => {
const filename = filenameInput.value;
const previewable = previewableExts.has(extname(filename));
togglePreviewDisplay(previewable);
Expand Down
70 changes: 70 additions & 0 deletions web_src/js/features/common-issue-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import $ from 'jquery';
import {isElemHidden, onInputDebounce, toggleElem} from '../utils/dom.js';
const {appSubUrl} = window.config;

const reIssueIndex = /^(\d+)$/; // eg: "123"
const reIssueSharpIndex = /^#(\d+)$/; // eg: "#123"
const reIssueOwnerRepoIndex = /^([-.\w]+)\/([-.\w]+)#(\d+)$/; // eg: "{owner}/{repo}#{index}"

// if the searchText can be parsed to an "issue goto link", return the link, otherwise return empty string
export function parseIssueListQuickGotoLink(repoLink, searchText) {
searchText = searchText.trim();
let targetUrl = '';
if (repoLink) {
// try to parse it in current repo
if (reIssueIndex.test(searchText)) {
targetUrl = `${repoLink}/issues/${searchText}`;
} else if (reIssueSharpIndex.test(searchText)) {
targetUrl = `${repoLink}/issues/${searchText.substr(1)}`;
}
} else {
// try to parse it for a global search (eg: "owner/repo#123")
const matchIssueOwnerRepoIndex = searchText.match(reIssueOwnerRepoIndex);
if (matchIssueOwnerRepoIndex) {
const [_, owner, repo, index] = matchIssueOwnerRepoIndex;
targetUrl = `${appSubUrl}/${owner}/${repo}/issues/${index}`;
}
}
return targetUrl;
}

export function initCommonIssueListQuickGoto() {
const $goto = $('#issue-list-quick-goto');
if (!$goto.length) return;

const $form = $goto.closest('form');
const $input = $form.find('input[name=q]');
const repoLink = $goto.attr('data-repo-link');

$form.on('submit', (e) => {
// if there is no goto button, or the form is submitted by non-quick-goto elements, submit the form directly
let doQuickGoto = !isElemHidden($goto);
const submitter = e.originalEvent.submitter;
if (submitter !== $form[0] && submitter !== $input[0] && submitter !== $goto[0]) doQuickGoto = false;
if (!doQuickGoto) return;

// if there is a goto button, use its link
e.preventDefault();
window.location.href = $goto.attr('data-issue-goto-link');
});

const onInput = async () => {
const searchText = $input.val();

// try to check whether the parsed goto link is valid
let targetUrl = parseIssueListQuickGotoLink(repoLink, searchText);
if (targetUrl) {
const res = await fetch(`${targetUrl}/info`);
if (res.status !== 200) targetUrl = '';
}

// if the input value has changed, then ignore the result
if ($input.val() !== searchText) return;

toggleElem($goto, Boolean(targetUrl));
$goto.attr('data-issue-goto-link', targetUrl);
};

$input.on('input', onInputDebounce(onInput));
onInput();
}
17 changes: 17 additions & 0 deletions web_src/js/features/common-issue-list.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {test, expect} from 'vitest';
import {parseIssueListQuickGotoLink} from './common-issue-list.js';

test('parseIssueListQuickGotoLink', () => {
expect(parseIssueListQuickGotoLink('/link', '')).toEqual('');
expect(parseIssueListQuickGotoLink('/link', 'abc')).toEqual('');
expect(parseIssueListQuickGotoLink('/link', '123')).toEqual('/link/issues/123');
expect(parseIssueListQuickGotoLink('/link', '#123')).toEqual('/link/issues/123');
expect(parseIssueListQuickGotoLink('/link', 'owner/repo#123')).toEqual('');

expect(parseIssueListQuickGotoLink('', '')).toEqual('');
expect(parseIssueListQuickGotoLink('', 'abc')).toEqual('');
expect(parseIssueListQuickGotoLink('', '123')).toEqual('');
expect(parseIssueListQuickGotoLink('', '#123')).toEqual('');
expect(parseIssueListQuickGotoLink('', 'owner/repo#')).toEqual('');
expect(parseIssueListQuickGotoLink('', 'owner/repo#123')).toEqual('/owner/repo/issues/123');
});
29 changes: 0 additions & 29 deletions web_src/js/features/repo-issue.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {showTemporaryTooltip, createTippy} from '../modules/tippy.js';
import {hideElem, showElem, toggleElem} from '../utils/dom.js';
import {setFileFolding} from './file-fold.js';
import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js';
import {parseIssueHref} from '../utils.js';

const {appSubUrl, csrfToken} = window.config;

Expand Down Expand Up @@ -638,34 +637,6 @@ export function initRepoIssueBranchSelect() {
$('#branch-select > .item').on('click', changeBranchSelect);
}

export function initRepoIssueGotoID() {
const issueidre = /^(?:\w+\/\w+#\d+|#\d+|\d+)$/;
const isGlobalIssuesArea = $('.repo.name.item').length > 0; // for global issues area or repository issues area
$('form.list-header-search').on('submit', (e) => {
const qval = e.target.q.value;
const aElm = document.activeElement;
if (!$('#hashtag-button').length || aElm.id === 'search-button' || (aElm.name === 'q' && !qval.includes('#')) || (isGlobalIssuesArea && !qval.includes('/')) || !issueidre.test(qval)) return;
const pathname = window.location.pathname;
let gotoUrl = qval.includes('/') ? `${qval.replace('#', '/issues/')}` : `${pathname}/${qval.replace('#', '')}`;
if (appSubUrl.length) {
gotoUrl = qval.includes('/') ? `/${appSubUrl}/${qval.replace('#', '/issues/')}` : `/${appSubUrl}/${pathname}/${qval.replace('#', '')}`;
}
const {owner, repo, type, index} = parseIssueHref(gotoUrl);
if (owner && repo && type && index) {
e.preventDefault();
window.location.href = gotoUrl;
}
});
$('form.list-header-search input[name=q]').on('input', (e) => {
const qval = e.target.value;
if (isGlobalIssuesArea && qval.includes('/') && issueidre.test(qval) || !isGlobalIssuesArea && issueidre.test(qval)) {
showElem($('#hashtag-button'));
} else {
hideElem($('#hashtag-button'));
}
});
}

export function initSingleCommentEditor($commentForm) {
// pages:
// * normal new issue/pr page, no status-button
Expand Down
5 changes: 3 additions & 2 deletions web_src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
initRepoIssueWipTitle,
initRepoPullRequestMergeInstruction,
initRepoPullRequestAllowMaintainerEdit,
initRepoPullRequestReview, initRepoIssueSidebarList, initRepoIssueGotoID
initRepoPullRequestReview, initRepoIssueSidebarList
} from './features/repo-issue.js';
import {
initRepoEllipsisButton,
Expand Down Expand Up @@ -81,6 +81,7 @@ import {initGlobalTooltips} from './modules/tippy.js';
import {initGiteaFomantic} from './modules/fomantic.js';
import {onDomReady} from './utils/dom.js';
import {initRepoIssueList} from './features/repo-issue-list.js';
import {initCommonIssueListQuickGoto} from './features/common-issue-list.js';

// Init Gitea's Fomantic settings
initGiteaFomantic();
Expand All @@ -98,6 +99,7 @@ onDomReady(() => {
initGlobalLinkActions();

initCommonOrganization();
initCommonIssueListQuickGoto();

initCompSearchUserBox();
initCompWebHookEditor();
Expand Down Expand Up @@ -175,5 +177,4 @@ onDomReady(() => {
initUserAuthWebAuthnRegister();
initUserSettings();
initRepoDiffView();
initRepoIssueGotoID();
});
13 changes: 13 additions & 0 deletions web_src/js/utils/dom.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {debounce} from 'throttle-debounce';

function elementsCall(el, func, ...args) {
if (typeof el === 'string' || el instanceof String) {
el = document.querySelectorAll(el);
Expand Down Expand Up @@ -42,6 +44,13 @@ export function toggleElem(el, force) {
elementsCall(el, toggleShown, force);
}

export function isElemHidden(el) {
const res = [];
elementsCall(el, (e) => res.push(e.classList.contains('gt-hidden')));
if (res.length > 1) throw new Error(`isElemHidden doesn't work for multiple elements`);
return res[0];
}

export function onDomReady(cb) {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', cb);
Expand Down Expand Up @@ -170,3 +179,7 @@ export function autosize(textarea, {viewportMarginBottom = 0} = {}) {
}
};
}

export function onInputDebounce(fn) {
return debounce(300, fn);
}