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).
+
+### `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
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.
}
export default function initGlobalCopyToClipboardListener() {
- document.addEventListener('click', async (e) => {
+ document.addEventListener('click', (e) => {
let target = e.target;
// 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++) {
}
if (text) {
e.preventDefault();
- try {
- await navigator.clipboard.writeText(text);
- onSuccess(target);
- } catch {
- if (fallbackCopyToClipboard(text)) {
+
+ (async() => {
+ try {
+ await navigator.clipboard.writeText(text);
onSuccess(target);
- } else {
- onError(target);
+ } catch {
+ if (fallbackCopyToClipboard(text)) {
+ onSuccess(target);
+ } else {
+ onError(target);
+ }
}
- }
+ })();
+
break;
}
target = target.parentElement;
let notificationSequenceNumber = 0;
export function initNotificationsTable() {
- $('#notification_table .button').on('click', async function () {
- const data = await updateNotification(
- $(this).data('url'),
- $(this).data('status'),
- $(this).data('page'),
- $(this).data('q'),
- $(this).data('notification-id'),
- );
-
- if ($(data).data('sequence-number') === notificationSequenceNumber) {
- $('#notification_div').replaceWith(data);
- initNotificationsTable();
- }
- await updateNotificationCount();
-
+ $('#notification_table .button').on('click', function () {
+ (async () => {
+ const data = await updateNotification(
+ $(this).data('url'),
+ $(this).data('status'),
+ $(this).data('page'),
+ $(this).data('q'),
+ $(this).data('notification-id'),
+ );
+
+ if ($(data).data('sequence-number') === notificationSequenceNumber) {
+ $('#notification_div').replaceWith(data);
+ initNotificationsTable();
+ }
+ await updateNotificationCount();
+ })();
return false;
});
}
}
const fn = (timeout, lastCount) => {
- setTimeout(async () => {
- await updateNotificationCountWithCallback(fn, timeout, lastCount);
+ setTimeout(() => {
+ const _promise = updateNotificationCountWithCallback(fn, timeout, lastCount);
}, timeout);
};
});
const url = new URL(window.location);
const params = url.searchParams;
- const updateGraph = async () => {
+ const updateGraph = () => {
const queryString = params.toString();
const ajaxUrl = new URL(url);
ajaxUrl.searchParams.set('div-only', 'true');
$('#rel-container').addClass('hide');
$('#rev-container').addClass('hide');
$('#loading-indicator').removeClass('hide');
-
- const div = $(await $.ajax(String(ajaxUrl)));
- $('#pagination').html(div.find('#pagination').html());
- $('#rel-container').html(div.find('#rel-container').html());
- $('#rev-container').html(div.find('#rev-container').html());
- $('#loading-indicator').addClass('hide');
- $('#rel-container').removeClass('hide');
- $('#rev-container').removeClass('hide');
+ (async () => {
+ const div = $(await $.ajax(String(ajaxUrl)));
+ $('#pagination').html(div.find('#pagination').html());
+ $('#rel-container').html(div.find('#rel-container').html());
+ $('#rev-container').html(div.find('#rev-container').html());
+ $('#loading-indicator').addClass('hide');
+ $('#rel-container').removeClass('hide');
+ $('#rev-container').removeClass('hide');
+ })();
};
const dropdownSelected = params.getAll('branch');
if (params.has('hide-pr-refs') && params.get('hide-pr-refs') === 'true') {
// Edit issue or comment content
$(document).on('click', '.edit-content', async function (event) {
+ event.preventDefault();
$(this).closest('.dropdown').find('.menu').toggle('visible');
const $segment = $(this).closest('.header').next();
const $editContentZone = $segment.find('.edit-content-zone');
$textarea.focus();
$simplemde.codemirror.focus();
});
- event.preventDefault();
});
initRepoIssueCommentDelete();
return;
}
- (async () => {
- await initRepoProjectSortable();
- })();
+ const _promise = initRepoProjectSortable();
$('.edit-project-board').each(function () {
const projectHeader = $(this).closest('.board-column-header');
}
const fn = (timeout) => {
- setTimeout(async () => {
- await updateStopwatchWithCallback(fn, timeout);
+ setTimeout(() => {
+ const _promise = updateStopwatchWithCallback(fn, timeout);
}, timeout);
};
return updateStopwatchData(data);
}
-async function updateStopwatchData(data) {
+function updateStopwatchData(data) {
const watch = data[0];
const btnEl = $('.active-stopwatch-trigger');
if (!watch) {
$('.stopwatch-cancel').attr('action', `${issueUrl}/times/stopwatch/cancel`);
$('.stopwatch-issue').text(`${repo_owner_name}/${repo_name}#${issue_index}`);
$('.stopwatch-time').text(prettyMilliseconds(seconds * 1000));
- await updateStopwatchTime(seconds);
+ updateStopwatchTime(seconds);
btnEl.removeClass('hidden');
}
return !!data.length;
}
-async function updateStopwatchTime(seconds) {
+function updateStopwatchTime(seconds) {
const secs = parseInt(seconds);
if (!Number.isFinite(secs)) return;