mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Add cropping support when modifying the user/org/repo avatar (#33498)
Fixed #33321 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		| @@ -195,8 +195,7 @@ | |||||||
| 				</div> | 				</div> | ||||||
|  |  | ||||||
| 				<div class="inline field tw-pl-4"> | 				<div class="inline field tw-pl-4"> | ||||||
| 					<label for="avatar">{{ctx.Locale.Tr "settings.choose_new_avatar"}}</label> | 					{{template "shared/avatar_upload_crop" dict "LabelText" (ctx.Locale.Tr "settings.choose_new_avatar")}} | ||||||
| 					<input name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp"> |  | ||||||
| 				</div> | 				</div> | ||||||
|  |  | ||||||
| 				<div class="field"> | 				<div class="field"> | ||||||
|   | |||||||
| @@ -89,10 +89,8 @@ | |||||||
| 					<form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data"> | 					<form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data"> | ||||||
| 						{{.CsrfTokenHtml}} | 						{{.CsrfTokenHtml}} | ||||||
| 						<div class="inline field"> | 						<div class="inline field"> | ||||||
| 							<label for="avatar">{{ctx.Locale.Tr "settings.choose_new_avatar"}}</label> | 							{{template "shared/avatar_upload_crop" dict "LabelText" (ctx.Locale.Tr "settings.choose_new_avatar")}} | ||||||
| 							<input name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp"> |  | ||||||
| 						</div> | 						</div> | ||||||
|  |  | ||||||
| 						<div class="field"> | 						<div class="field"> | ||||||
| 							<button class="ui primary button">{{ctx.Locale.Tr "settings.update_avatar"}}</button> | 							<button class="ui primary button">{{ctx.Locale.Tr "settings.update_avatar"}}</button> | ||||||
| 							<button class="ui red button link-action" data-url="{{.Link}}/avatar/delete">{{ctx.Locale.Tr "settings.delete_current_avatar"}}</button> | 							<button class="ui red button link-action" data-url="{{.Link}}/avatar/delete">{{ctx.Locale.Tr "settings.delete_current_avatar"}}</button> | ||||||
|   | |||||||
| @@ -40,8 +40,7 @@ | |||||||
| 			<form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data"> | 			<form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data"> | ||||||
| 				{{.CsrfTokenHtml}} | 				{{.CsrfTokenHtml}} | ||||||
| 				<div class="inline field"> | 				<div class="inline field"> | ||||||
| 					<label for="avatar">{{ctx.Locale.Tr "settings.choose_new_avatar"}}</label> | 					{{template "shared/avatar_upload_crop" dict "LabelText" (ctx.Locale.Tr "settings.choose_new_avatar")}} | ||||||
| 					<input name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp"> |  | ||||||
| 				</div> | 				</div> | ||||||
| 				<div class="field"> | 				<div class="field"> | ||||||
| 					<button class="ui primary button">{{ctx.Locale.Tr "settings.update_avatar"}}</button> | 					<button class="ui primary button">{{ctx.Locale.Tr "settings.update_avatar"}}</button> | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								templates/shared/avatar_upload_crop.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								templates/shared/avatar_upload_crop.tmpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | {{- /* we do not need to set for/id here, global aria init code will add them automatically */ -}} | ||||||
|  | <label>{{.LabelText}}</label> | ||||||
|  | <input class="avatar-file-with-cropper" name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp"> | ||||||
|  | {{- /* the cropper-panel must be next sibling of the input "avatar" */ -}} | ||||||
|  | <div class="cropper-panel tw-hidden"> | ||||||
|  | 	<div class="tw-my-2">{{ctx.Locale.Tr "settings.cropper_prompt"}}</div> | ||||||
|  | 	<div class="cropper-wrapper"><img class="cropper-source" src alt></div> | ||||||
|  | </div> | ||||||
| @@ -124,13 +124,7 @@ | |||||||
| 				</div> | 				</div> | ||||||
|  |  | ||||||
| 				<div class="inline field tw-pl-4"> | 				<div class="inline field tw-pl-4"> | ||||||
| 					<label for="new-avatar">{{ctx.Locale.Tr "settings.choose_new_avatar"}}</label> | 					{{template "shared/avatar_upload_crop" dict "LabelText" (ctx.Locale.Tr "settings.choose_new_avatar")}} | ||||||
| 					<input id="new-avatar" name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp"> |  | ||||||
| 				</div> |  | ||||||
|  |  | ||||||
| 				<div class="field tw-pl-4 cropper-panel tw-hidden"> |  | ||||||
| 					<div>{{ctx.Locale.Tr "settings.cropper_prompt"}}</div> |  | ||||||
| 					<div class="cropper-wrapper"><img class="cropper-source" src alt></div> |  | ||||||
| 				</div> | 				</div> | ||||||
|  |  | ||||||
| 				<div class="field"> | 				<div class="field"> | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| @import "cropperjs/dist/cropper.css"; | @import "cropperjs/dist/cropper.css"; | ||||||
|  |  | ||||||
| .page-content.user.profile .cropper-panel .cropper-wrapper { | .avatar-file-with-cropper + .cropper-panel .cropper-wrapper { | ||||||
|   max-width: 400px; |   max-width: 400px; | ||||||
|   max-height: 400px; |   max-height: 400px; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| import $ from 'jquery'; | import $ from 'jquery'; | ||||||
| import {checkAppUrl} from '../common-page.ts'; | import {checkAppUrl} from '../common-page.ts'; | ||||||
| import {hideElem, showElem, toggleElem} from '../../utils/dom.ts'; | import {hideElem, queryElems, showElem, toggleElem} from '../../utils/dom.ts'; | ||||||
| import {POST} from '../../modules/fetch.ts'; | import {POST} from '../../modules/fetch.ts'; | ||||||
|  | import {initAvatarUploaderWithCropper} from '../comp/Cropper.ts'; | ||||||
|  |  | ||||||
| const {appSubUrl} = window.config; | const {appSubUrl} = window.config; | ||||||
|  |  | ||||||
| @@ -258,4 +259,6 @@ export function initAdminCommon(): void { | |||||||
|       window.location.href = this.getAttribute('data-redirect'); |       window.location.href = this.getAttribute('data-redirect'); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   queryElems(document, '.avatar-file-with-cropper', initAvatarUploaderWithCropper); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import {initCompLabelEdit} from './comp/LabelEdit.ts'; | import {initCompLabelEdit} from './comp/LabelEdit.ts'; | ||||||
| import {toggleElem} from '../utils/dom.ts'; | import {queryElems, toggleElem} from '../utils/dom.ts'; | ||||||
|  | import {initAvatarUploaderWithCropper} from './comp/Cropper.ts'; | ||||||
|  |  | ||||||
| export function initCommonOrganization() { | export function initCommonOrganization() { | ||||||
|   if (!document.querySelectorAll('.organization').length) { |   if (!document.querySelectorAll('.organization').length) { | ||||||
| @@ -13,4 +14,6 @@ export function initCommonOrganization() { | |||||||
|  |  | ||||||
|   // Labels |   // Labels | ||||||
|   initCompLabelEdit('.page-content.organization.settings.labels'); |   initCompLabelEdit('.page-content.organization.settings.labels'); | ||||||
|  |  | ||||||
|  |   queryElems(document, '.avatar-file-with-cropper', initAvatarUploaderWithCropper); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ type CropperOpts = { | |||||||
|   fileInput: HTMLInputElement, |   fileInput: HTMLInputElement, | ||||||
| } | } | ||||||
|  |  | ||||||
| export async function initCompCropper({container, fileInput, imageSource}: CropperOpts) { | async function initCompCropper({container, fileInput, imageSource}: CropperOpts) { | ||||||
|   const {default: Cropper} = await import(/* webpackChunkName: "cropperjs" */'cropperjs'); |   const {default: Cropper} = await import(/* webpackChunkName: "cropperjs" */'cropperjs'); | ||||||
|   let currentFileName = ''; |   let currentFileName = ''; | ||||||
|   let currentFileLastModified = 0; |   let currentFileLastModified = 0; | ||||||
| @@ -38,3 +38,10 @@ export async function initCompCropper({container, fileInput, imageSource}: Cropp | |||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export async function initAvatarUploaderWithCropper(fileInput: HTMLInputElement) { | ||||||
|  |   const panel = fileInput.nextElementSibling as HTMLElement; | ||||||
|  |   if (!panel?.matches('.cropper-panel')) throw new Error('Missing cropper panel for avatar uploader'); | ||||||
|  |   const imageSource = panel.querySelector<HTMLImageElement>('.cropper-source'); | ||||||
|  |   await initCompCropper({container: panel, fileInput, imageSource}); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import {minimatch} from 'minimatch'; | |||||||
| import {createMonaco} from './codeeditor.ts'; | import {createMonaco} from './codeeditor.ts'; | ||||||
| import {onInputDebounce, queryElems, toggleElem} from '../utils/dom.ts'; | import {onInputDebounce, queryElems, toggleElem} from '../utils/dom.ts'; | ||||||
| import {POST} from '../modules/fetch.ts'; | import {POST} from '../modules/fetch.ts'; | ||||||
|  | import {initAvatarUploaderWithCropper} from './comp/Cropper.ts'; | ||||||
| import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts'; | import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts'; | ||||||
|  |  | ||||||
| const {appSubUrl, csrfToken} = window.config; | const {appSubUrl, csrfToken} = window.config; | ||||||
| @@ -156,4 +157,6 @@ export function initRepoSettings() { | |||||||
|   initRepoSettingsSearchTeamBox(); |   initRepoSettingsSearchTeamBox(); | ||||||
|   initRepoSettingsGitHook(); |   initRepoSettingsGitHook(); | ||||||
|   initRepoSettingsBranchesDrag(); |   initRepoSettingsBranchesDrag(); | ||||||
|  |  | ||||||
|  |   queryElems(document, '.avatar-file-with-cropper', initAvatarUploaderWithCropper); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,17 +1,10 @@ | |||||||
| import {hideElem, showElem} from '../utils/dom.ts'; | import {hideElem, queryElems, showElem} from '../utils/dom.ts'; | ||||||
| import {initCompCropper} from './comp/Cropper.ts'; | import {initAvatarUploaderWithCropper} from './comp/Cropper.ts'; | ||||||
|  |  | ||||||
| function initUserSettingsAvatarCropper() { |  | ||||||
|   const fileInput = document.querySelector<HTMLInputElement>('#new-avatar'); |  | ||||||
|   const container = document.querySelector<HTMLElement>('.user.settings.profile .cropper-panel'); |  | ||||||
|   const imageSource = container.querySelector<HTMLImageElement>('.cropper-source'); |  | ||||||
|   initCompCropper({container, fileInput, imageSource}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function initUserSettings() { | export function initUserSettings() { | ||||||
|   if (!document.querySelector('.user.settings.profile')) return; |   if (!document.querySelector('.user.settings.profile')) return; | ||||||
|  |  | ||||||
|   initUserSettingsAvatarCropper(); |   queryElems(document, '.avatar-file-with-cropper', initAvatarUploaderWithCropper); | ||||||
|  |  | ||||||
|   const usernameInput = document.querySelector<HTMLInputElement>('#username'); |   const usernameInput = document.querySelector<HTMLInputElement>('#username'); | ||||||
|   if (!usernameInput) return; |   if (!usernameInput) return; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Kerwin Bryant
					Kerwin Bryant