Improve "generate new access token" form (#33730)

Fix: https://github.com/go-gitea/gitea/issues/33519

As discussed in [PR
#33614](https://github.com/go-gitea/gitea/pull/33614), the
ScopedAccessTokenSelector Vue component is not particularly useful.

This PR removes the component and reverts to using HTML templates. It
also introduces some (hopefully) useful refactoring.

The Vue component was causing the UX bug reported in the linked issue.
Required form fields are now properly working, as expected (see
screenshot).

![Screenshot from 2025-02-25
22-00-28](https://github.com/user-attachments/assets/41167854-0718-48b0-a3ee-75ca3a7b8b20)

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Guillaume 2025-02-27 14:40:12 -05:00 committed by GitHub
parent 8362a41559
commit 303af554c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 138 additions and 298 deletions

View file

@ -19,7 +19,6 @@
@import "./modules/dimmer.css";
@import "./modules/modal.css";
@import "./modules/select.css";
@import "./modules/tippy.css";
@import "./modules/breadcrumb.css";
@import "./modules/comment.css";

View file

@ -119,3 +119,13 @@ input[type="radio"] {
.ui.toggle.checkbox input:focus:checked ~ label::before {
background: var(--color-primary) !important;
}
label.gt-checkbox {
display: inline-flex;
align-items: center;
gap: 0.25em;
}
.ui.form .field > label.gt-checkbox {
display: flex;
}

View file

@ -1,25 +0,0 @@
.gitea-select {
position: relative;
}
.gitea-select select {
appearance: none; /* hide default triangle */
}
/* ::before and ::after pseudo elements don't work on select elements,
so we need to put it on the parent. */
.gitea-select::after {
position: absolute;
top: 12px;
right: 8px;
pointer-events: none;
content: "";
width: 14px;
height: 14px;
mask-size: cover;
-webkit-mask-size: cover;
mask-image: var(--octicon-chevron-right);
-webkit-mask-image: var(--octicon-chevron-right);
transform: rotate(90deg); /* point the chevron down */
background: currentcolor;
}

View file

@ -1,81 +0,0 @@
<script lang="ts" setup>
import {computed, onMounted, onUnmounted} from 'vue';
import {hideElem, showElem} from '../utils/dom.ts';
const props = defineProps<{
isAdmin: boolean;
noAccessLabel: string;
readLabel: string;
writeLabel: string;
}>();
const categories = computed(() => {
const categories = [
'activitypub',
];
if (props.isAdmin) {
categories.push('admin');
}
categories.push(
'issue',
'misc',
'notification',
'organization',
'package',
'repository',
'user');
return categories;
});
onMounted(() => {
document.querySelector('#scoped-access-submit').addEventListener('click', onClickSubmit);
});
onUnmounted(() => {
document.querySelector('#scoped-access-submit').removeEventListener('click', onClickSubmit);
});
function onClickSubmit(e: Event) {
e.preventDefault();
const warningEl = document.querySelector('#scoped-access-warning');
// check that at least one scope has been selected
for (const el of document.querySelectorAll<HTMLInputElement>('.access-token-select')) {
if (el.value) {
// Hide the error if it was visible from previous attempt.
hideElem(warningEl);
// Submit the form.
document.querySelector<HTMLFormElement>('#scoped-access-form').submit();
// Don't show the warning.
return;
}
}
// no scopes selected, show validation error
showElem(warningEl);
}
</script>
<template>
<div v-for="category in categories" :key="category" class="field tw-pl-1 tw-pb-1 access-token-category">
<label class="category-label" :for="'access-token-scope-' + category">
{{ category }}
</label>
<div class="gitea-select">
<select
class="ui selection access-token-select"
name="scope"
:id="'access-token-scope-' + category"
>
<option value="">
{{ noAccessLabel }}
</option>
<option :value="'read:' + category">
{{ readLabel }}
</option>
<option :value="'write:' + category">
{{ writeLabel }}
</option>
</select>
</div>
</div>
</template>

View file

@ -1,20 +0,0 @@
import {createApp} from 'vue';
export async function initScopedAccessTokenCategories() {
const el = document.querySelector('#scoped-access-token-selector');
if (!el) return;
const {default: ScopedAccessTokenSelector} = await import(/* webpackChunkName: "scoped-access-token-selector" */'../components/ScopedAccessTokenSelector.vue');
try {
const View = createApp(ScopedAccessTokenSelector, {
isAdmin: JSON.parse(el.getAttribute('data-is-admin')),
noAccessLabel: el.getAttribute('data-no-access-label'),
readLabel: el.getAttribute('data-read-label'),
writeLabel: el.getAttribute('data-write-label'),
});
View.mount(el);
} catch (err) {
console.error('ScopedAccessTokenSelector failed to load', err);
el.textContent = el.getAttribute('data-locale-component-failed-to-load');
}
}

View file

@ -68,7 +68,6 @@ import {initColorPickers} from './features/colorpicker.ts';
import {initAdminSelfCheck} from './features/admin/selfcheck.ts';
import {initOAuth2SettingsDisableCheckbox} from './features/oauth2-settings.ts';
import {initGlobalFetchAction} from './features/common-fetch-action.ts';
import {initScopedAccessTokenCategories} from './features/scoped-access-token.ts';
import {
initFootLanguageMenu,
initGlobalDropdown,
@ -209,7 +208,6 @@ onDomReady(() => {
initUserSettings,
initRepoDiffView,
initPdfViewer,
initScopedAccessTokenCategories,
initColorPickers,
initOAuth2SettingsDisableCheckbox,