mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Backport #25109 by @jtran The current UI to create API access tokens uses checkboxes that have a complicated relationship where some need to be checked and/or disabled in certain states. It also requires that a user interact with it to understand what their options really are. This branch changes to use `<select>`s. It better fits the available options, and it's closer to [GitHub's UI](https://github.com/settings/personal-access-tokens/new), which is good, in my opinion. It's more mobile friendly since the tap-areas are larger. If we ever add more permissions, like Maintainer, there's a natural place that doesn't take up more screen real-estate. This branch also fixes a few minor issues: - Hide the error about selecting at least one permission after second submission - Fix help description to call it "authorization" since that's what permissions are about (not authentication) Related: #24767. <img width="883" alt="Screenshot 2023-06-07 at 5 07 34 PM" src="https://github.com/go-gitea/gitea/assets/10803/6b63d807-c9be-4a4b-8e53-ecab6cbb8f76"> --- When it's open: <img width="881" alt="Screenshot 2023-06-07 at 5 07 59 PM" src="https://github.com/go-gitea/gitea/assets/10803/2432c6d0-39c2-4ca4-820e-c878ffdbfb69"> Co-authored-by: Jonathan Tran <jon@allspice.io>
This commit is contained in:
		| @@ -1,97 +1,100 @@ | ||||
| <template> | ||||
|   <div class="scoped-access-token-category"> | ||||
|     <div class="field gt-pl-2"> | ||||
|       <label class="checkbox-label"> | ||||
|         <input | ||||
|           ref="category" | ||||
|           v-model="categorySelected" | ||||
|           class="scope-checkbox scoped-access-token-input" | ||||
|           type="checkbox" | ||||
|           name="scope" | ||||
|           :value="'write:' + category" | ||||
|           @input="onCategoryInput" | ||||
|         > | ||||
|         {{ category }} | ||||
|       </label> | ||||
|     </div> | ||||
|     <div class="field gt-pl-4"> | ||||
|       <div class="inline field"> | ||||
|         <label class="checkbox-label"> | ||||
|           <input | ||||
|             ref="read" | ||||
|             v-model="readSelected" | ||||
|             :disabled="disableIndividual || writeSelected" | ||||
|             class="scope-checkbox scoped-access-token-input" | ||||
|             type="checkbox" | ||||
|             name="scope" | ||||
|             :value="'read:' + category" | ||||
|             @input="onIndividualInput" | ||||
|           > | ||||
|           read:{{ category }} | ||||
|         </label> | ||||
|       </div> | ||||
|       <div class="inline field"> | ||||
|         <label class="checkbox-label"> | ||||
|           <input | ||||
|             ref="write" | ||||
|             v-model="writeSelected" | ||||
|             :disabled="disableIndividual" | ||||
|             class="scope-checkbox scoped-access-token-input" | ||||
|             type="checkbox" | ||||
|             name="scope" | ||||
|             :value="'write:' + category" | ||||
|             @input="onIndividualInput" | ||||
|           > | ||||
|           write:{{ category }} | ||||
|         </label> | ||||
|       </div> | ||||
|   <div v-for="category in categories" :key="category" class="field gt-pl-2 gt-pb-2 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> | ||||
|  | ||||
| <script> | ||||
| import {createApp} from 'vue'; | ||||
| import {showElem} from '../utils/dom.js'; | ||||
| import {hideElem, showElem} from '../utils/dom.js'; | ||||
|  | ||||
| const sfc = { | ||||
|   props: { | ||||
|     category: { | ||||
|     isAdmin: { | ||||
|       type: Boolean, | ||||
|       required: true, | ||||
|     }, | ||||
|     noAccessLabel: { | ||||
|       type: String, | ||||
|       required: true, | ||||
|     }, | ||||
|     readLabel: { | ||||
|       type: String, | ||||
|       required: true, | ||||
|     }, | ||||
|     writeLabel: { | ||||
|       type: String, | ||||
|       required: true, | ||||
|     }, | ||||
|   }, | ||||
|  | ||||
|   data: () => ({ | ||||
|     categorySelected: false, | ||||
|     disableIndividual: false, | ||||
|     readSelected: false, | ||||
|     writeSelected: false, | ||||
|   }), | ||||
|   computed: { | ||||
|     categories() { | ||||
|       const categories = [ | ||||
|         'activitypub', | ||||
|       ]; | ||||
|       if (this.isAdmin) { | ||||
|         categories.push('admin'); | ||||
|       } | ||||
|       categories.push( | ||||
|         'issue', | ||||
|         'misc', | ||||
|         'notification', | ||||
|         'organization', | ||||
|         'package', | ||||
|         'repository', | ||||
|         'user'); | ||||
|       return categories; | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   mounted() { | ||||
|     document.getElementById('scoped-access-submit').addEventListener('click', this.onClickSubmit); | ||||
|   }, | ||||
|  | ||||
|   unmounted() { | ||||
|     document.getElementById('scoped-access-submit').removeEventListener('click', this.onClickSubmit); | ||||
|   }, | ||||
|  | ||||
|   methods: { | ||||
|     /** | ||||
|      * When entire category is toggled | ||||
|      * @param {Event} e | ||||
|      */ | ||||
|     onCategoryInput(e) { | ||||
|     onClickSubmit(e) { | ||||
|       e.preventDefault(); | ||||
|       this.disableIndividual = this.$refs.category.checked; | ||||
|       this.writeSelected = this.$refs.category.checked; | ||||
|       this.readSelected = this.$refs.category.checked; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * When an individual level of category is toggled | ||||
|      * @param {Event} e | ||||
|      */ | ||||
|     onIndividualInput(e) { | ||||
|       e.preventDefault(); | ||||
|       if (this.$refs.write.checked) { | ||||
|         this.readSelected = true; | ||||
|       const warningEl = document.getElementById('scoped-access-warning'); | ||||
|       // check that at least one scope has been selected | ||||
|       for (const el of document.getElementsByClassName('access-token-select')) { | ||||
|         if (el.value) { | ||||
|           // Hide the error if it was visible from previous attempt. | ||||
|           hideElem(warningEl); | ||||
|           // Submit the form. | ||||
|           document.getElementById('scoped-access-form').submit(); | ||||
|           // Don't show the warning. | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|       this.categorySelected = this.$refs.write.checked; | ||||
|     }, | ||||
|   } | ||||
|       // no scopes selected, show validation error | ||||
|       showElem(warningEl); | ||||
|     } | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export default sfc; | ||||
| @@ -100,39 +103,11 @@ export default sfc; | ||||
|  * Initialize category toggle sections | ||||
|  */ | ||||
| export function initScopedAccessTokenCategories() { | ||||
|   for (const el of document.getElementsByTagName('scoped-access-token-category')) { | ||||
|     const category = el.getAttribute('category'); | ||||
|     createApp(sfc, { | ||||
|       category, | ||||
|     }).mount(el); | ||||
|   for (const el of document.getElementsByClassName('scoped-access-token-mount')) { | ||||
|     createApp({}) | ||||
|       .component('scoped-access-token-selector', sfc) | ||||
|       .mount(el); | ||||
|   } | ||||
|  | ||||
|   document.getElementById('scoped-access-submit')?.addEventListener('click', (e) => { | ||||
|     e.preventDefault(); | ||||
|     // check that at least one scope has been selected | ||||
|     for (const el of document.getElementsByClassName('scoped-access-token-input')) { | ||||
|       if (el.checked) { | ||||
|         document.getElementById('scoped-access-form').submit(); | ||||
|       } | ||||
|     } | ||||
|     // no scopes selected, show validation error | ||||
|     showElem(document.getElementById('scoped-access-warning')); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .scoped-access-token-category { | ||||
|   padding-top: 10px; | ||||
|   padding-bottom: 10px; | ||||
| } | ||||
|  | ||||
| .checkbox-label { | ||||
|   cursor: pointer; | ||||
| } | ||||
|  | ||||
| .scope-checkbox { | ||||
|   margin: 4px 5px 0 0; | ||||
| } | ||||
| </style> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Giteabot
					Giteabot