mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-27 21:03:56 +01:00
Fix some incorrect async functions, improve frontend document. (#17597)
This commit is contained in:
parent
0db7a32b92
commit
7f802631c5
7 changed files with 106 additions and 43 deletions
|
@ -39,6 +39,65 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h
|
||||||
6. The backend can pass complex data to the frontend by using `ctx.PageData["myModuleData"] = map[]{}`
|
6. The backend can pass complex data to the frontend by using `ctx.PageData["myModuleData"] = map[]{}`
|
||||||
7. Simple pages and SEO-related pages use Go HTML Template render to generate static Fomantic-UI HTML output. Complex pages can use Vue2 (or Vue3 in future).
|
7. Simple pages and SEO-related pages use Go HTML Template render to generate static Fomantic-UI HTML output. Complex pages can use Vue2 (or Vue3 in future).
|
||||||
|
|
||||||
|
|
||||||
|
### `async` Functions
|
||||||
|
|
||||||
|
Only mark a function as `async` if and only if there are `await` calls
|
||||||
|
or `Promise` returns inside the function.
|
||||||
|
|
||||||
|
It's not recommended to use `async` event listeners, which may lead to problems.
|
||||||
|
The reason is that the code after await is executed outside the event dispatch.
|
||||||
|
Reference: https://github.com/github/eslint-plugin-github/blob/main/docs/rules/async-preventdefault.md
|
||||||
|
|
||||||
|
If we want to call an `async` function in a non-async context,
|
||||||
|
it's recommended to use `const _promise = asyncFoo()` to tell readers
|
||||||
|
that this is done by purpose, we want to call the async function and ignore the Promise.
|
||||||
|
Some lint rules and IDEs also have warnings if the returned Promise is not handled.
|
||||||
|
|
||||||
|
#### DOM Event Listener
|
||||||
|
|
||||||
|
```js
|
||||||
|
el.addEventListener('click', (e) => {
|
||||||
|
(async () => {
|
||||||
|
await asyncFoo(); // recommended
|
||||||
|
// then we shound't do e.preventDefault() after await, no effect
|
||||||
|
})();
|
||||||
|
|
||||||
|
const _promise = asyncFoo(); // recommended
|
||||||
|
|
||||||
|
e.preventDefault(); // correct
|
||||||
|
});
|
||||||
|
|
||||||
|
el.addEventListener('async', async (e) => { // not recommended but acceptable
|
||||||
|
e.preventDefault(); // acceptable
|
||||||
|
await asyncFoo(); // skip out event dispath
|
||||||
|
e.preventDefault(); // WRONG
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### jQuery Event Listener
|
||||||
|
|
||||||
|
```js
|
||||||
|
$('#el').on('click', (e) => {
|
||||||
|
(async () => {
|
||||||
|
await asyncFoo(); // recommended
|
||||||
|
// then we shound't do e.preventDefault() after await, no effect
|
||||||
|
})();
|
||||||
|
|
||||||
|
const _promise = asyncFoo(); // recommended
|
||||||
|
|
||||||
|
e.preventDefault(); // correct
|
||||||
|
return false; // correct
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#el').on('click', async (e) => { // not recommended but acceptable
|
||||||
|
e.preventDefault(); // acceptable
|
||||||
|
return false; // WRONG, jQuery expects the returned value is a boolean, not a Promise
|
||||||
|
await asyncFoo(); // skip out event dispath
|
||||||
|
return false; // WRONG
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### Vue2/Vue3 and JSX
|
### Vue2/Vue3 and JSX
|
||||||
|
|
||||||
Gitea is using Vue2 now, we plan to upgrade to Vue3. We decided not to introduce JSX to keep the HTML and the JavaScript code separated.
|
Gitea is using Vue2 now, we plan to upgrade to Vue3. We decided not to introduce JSX to keep the HTML and the JavaScript code separated.
|
||||||
|
|
|
@ -46,7 +46,7 @@ function fallbackCopyToClipboard(text) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function initGlobalCopyToClipboardListener() {
|
export default function initGlobalCopyToClipboardListener() {
|
||||||
document.addEventListener('click', async (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
let target = e.target;
|
let target = e.target;
|
||||||
// in case <button data-clipboard-text><svg></button>, so we just search up to 3 levels for performance.
|
// in case <button data-clipboard-text><svg></button>, so we just search up to 3 levels for performance.
|
||||||
for (let i = 0; i < 3 && target; i++) {
|
for (let i = 0; i < 3 && target; i++) {
|
||||||
|
@ -58,6 +58,8 @@ export default function initGlobalCopyToClipboardListener() {
|
||||||
}
|
}
|
||||||
if (text) {
|
if (text) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
(async() => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
onSuccess(target);
|
onSuccess(target);
|
||||||
|
@ -68,6 +70,8 @@ export default function initGlobalCopyToClipboardListener() {
|
||||||
onError(target);
|
onError(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
target = target.parentElement;
|
target = target.parentElement;
|
||||||
|
|
|
@ -3,7 +3,8 @@ const {appSubUrl, csrfToken, notificationSettings} = window.config;
|
||||||
let notificationSequenceNumber = 0;
|
let notificationSequenceNumber = 0;
|
||||||
|
|
||||||
export function initNotificationsTable() {
|
export function initNotificationsTable() {
|
||||||
$('#notification_table .button').on('click', async function () {
|
$('#notification_table .button').on('click', function () {
|
||||||
|
(async () => {
|
||||||
const data = await updateNotification(
|
const data = await updateNotification(
|
||||||
$(this).data('url'),
|
$(this).data('url'),
|
||||||
$(this).data('status'),
|
$(this).data('status'),
|
||||||
|
@ -17,7 +18,7 @@ export function initNotificationsTable() {
|
||||||
initNotificationsTable();
|
initNotificationsTable();
|
||||||
}
|
}
|
||||||
await updateNotificationCount();
|
await updateNotificationCount();
|
||||||
|
})();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -104,8 +105,8 @@ export function initNotificationCount() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn = (timeout, lastCount) => {
|
const fn = (timeout, lastCount) => {
|
||||||
setTimeout(async () => {
|
setTimeout(() => {
|
||||||
await updateNotificationCountWithCallback(fn, timeout, lastCount);
|
const _promise = updateNotificationCountWithCallback(fn, timeout, lastCount);
|
||||||
}, timeout);
|
}, timeout);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default function initRepoGraphGit() {
|
||||||
});
|
});
|
||||||
const url = new URL(window.location);
|
const url = new URL(window.location);
|
||||||
const params = url.searchParams;
|
const params = url.searchParams;
|
||||||
const updateGraph = async () => {
|
const updateGraph = () => {
|
||||||
const queryString = params.toString();
|
const queryString = params.toString();
|
||||||
const ajaxUrl = new URL(url);
|
const ajaxUrl = new URL(url);
|
||||||
ajaxUrl.searchParams.set('div-only', 'true');
|
ajaxUrl.searchParams.set('div-only', 'true');
|
||||||
|
@ -57,7 +57,7 @@ export default function initRepoGraphGit() {
|
||||||
$('#rel-container').addClass('hide');
|
$('#rel-container').addClass('hide');
|
||||||
$('#rev-container').addClass('hide');
|
$('#rev-container').addClass('hide');
|
||||||
$('#loading-indicator').removeClass('hide');
|
$('#loading-indicator').removeClass('hide');
|
||||||
|
(async () => {
|
||||||
const div = $(await $.ajax(String(ajaxUrl)));
|
const div = $(await $.ajax(String(ajaxUrl)));
|
||||||
$('#pagination').html(div.find('#pagination').html());
|
$('#pagination').html(div.find('#pagination').html());
|
||||||
$('#rel-container').html(div.find('#rel-container').html());
|
$('#rel-container').html(div.find('#rel-container').html());
|
||||||
|
@ -65,6 +65,7 @@ export default function initRepoGraphGit() {
|
||||||
$('#loading-indicator').addClass('hide');
|
$('#loading-indicator').addClass('hide');
|
||||||
$('#rel-container').removeClass('hide');
|
$('#rel-container').removeClass('hide');
|
||||||
$('#rev-container').removeClass('hide');
|
$('#rev-container').removeClass('hide');
|
||||||
|
})();
|
||||||
};
|
};
|
||||||
const dropdownSelected = params.getAll('branch');
|
const dropdownSelected = params.getAll('branch');
|
||||||
if (params.has('hide-pr-refs') && params.get('hide-pr-refs') === 'true') {
|
if (params.has('hide-pr-refs') && params.get('hide-pr-refs') === 'true') {
|
||||||
|
|
|
@ -351,6 +351,7 @@ export function initRepository() {
|
||||||
|
|
||||||
// Edit issue or comment content
|
// Edit issue or comment content
|
||||||
$(document).on('click', '.edit-content', async function (event) {
|
$(document).on('click', '.edit-content', async function (event) {
|
||||||
|
event.preventDefault();
|
||||||
$(this).closest('.dropdown').find('.menu').toggle('visible');
|
$(this).closest('.dropdown').find('.menu').toggle('visible');
|
||||||
const $segment = $(this).closest('.header').next();
|
const $segment = $(this).closest('.header').next();
|
||||||
const $editContentZone = $segment.find('.edit-content-zone');
|
const $editContentZone = $segment.find('.edit-content-zone');
|
||||||
|
@ -511,7 +512,6 @@ export function initRepository() {
|
||||||
$textarea.focus();
|
$textarea.focus();
|
||||||
$simplemde.codemirror.focus();
|
$simplemde.codemirror.focus();
|
||||||
});
|
});
|
||||||
event.preventDefault();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
initRepoIssueCommentDelete();
|
initRepoIssueCommentDelete();
|
||||||
|
|
|
@ -63,9 +63,7 @@ export default function initRepoProject() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
const _promise = initRepoProjectSortable();
|
||||||
await initRepoProjectSortable();
|
|
||||||
})();
|
|
||||||
|
|
||||||
$('.edit-project-board').each(function () {
|
$('.edit-project-board').each(function () {
|
||||||
const projectHeader = $(this).closest('.board-column-header');
|
const projectHeader = $(this).closest('.board-column-header');
|
||||||
|
|
|
@ -82,8 +82,8 @@ export function initStopwatch() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn = (timeout) => {
|
const fn = (timeout) => {
|
||||||
setTimeout(async () => {
|
setTimeout(() => {
|
||||||
await updateStopwatchWithCallback(fn, timeout);
|
const _promise = updateStopwatchWithCallback(fn, timeout);
|
||||||
}, timeout);
|
}, timeout);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ async function updateStopwatch() {
|
||||||
return updateStopwatchData(data);
|
return updateStopwatchData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateStopwatchData(data) {
|
function updateStopwatchData(data) {
|
||||||
const watch = data[0];
|
const watch = data[0];
|
||||||
const btnEl = $('.active-stopwatch-trigger');
|
const btnEl = $('.active-stopwatch-trigger');
|
||||||
if (!watch) {
|
if (!watch) {
|
||||||
|
@ -135,14 +135,14 @@ async function updateStopwatchData(data) {
|
||||||
$('.stopwatch-cancel').attr('action', `${issueUrl}/times/stopwatch/cancel`);
|
$('.stopwatch-cancel').attr('action', `${issueUrl}/times/stopwatch/cancel`);
|
||||||
$('.stopwatch-issue').text(`${repo_owner_name}/${repo_name}#${issue_index}`);
|
$('.stopwatch-issue').text(`${repo_owner_name}/${repo_name}#${issue_index}`);
|
||||||
$('.stopwatch-time').text(prettyMilliseconds(seconds * 1000));
|
$('.stopwatch-time').text(prettyMilliseconds(seconds * 1000));
|
||||||
await updateStopwatchTime(seconds);
|
updateStopwatchTime(seconds);
|
||||||
btnEl.removeClass('hidden');
|
btnEl.removeClass('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!data.length;
|
return !!data.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateStopwatchTime(seconds) {
|
function updateStopwatchTime(seconds) {
|
||||||
const secs = parseInt(seconds);
|
const secs = parseInt(seconds);
|
||||||
if (!Number.isFinite(secs)) return;
|
if (!Number.isFinite(secs)) return;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue