mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 03:02:14 +01:00 
			
		
		
		
	Merge branch 'main' into feat-32257-add-comments-unchanged-lines-and-show
This commit is contained in:
		
						commit
						a03abb516f
					
				| @ -5,15 +5,15 @@ import {POST} from '../../modules/fetch.ts'; | |||||||
| 
 | 
 | ||||||
| const {appSubUrl} = window.config; | const {appSubUrl} = window.config; | ||||||
| 
 | 
 | ||||||
| function onSecurityProtocolChange() { | function onSecurityProtocolChange(): void { | ||||||
|   if (Number(document.querySelector('#security_protocol')?.value) > 0) { |   if (Number(document.querySelector<HTMLInputElement>('#security_protocol')?.value) > 0) { | ||||||
|     showElem('.has-tls'); |     showElem('.has-tls'); | ||||||
|   } else { |   } else { | ||||||
|     hideElem('.has-tls'); |     hideElem('.has-tls'); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function initAdminCommon() { | export function initAdminCommon(): void { | ||||||
|   if (!document.querySelector('.page-content.admin')) return; |   if (!document.querySelector('.page-content.admin')) return; | ||||||
| 
 | 
 | ||||||
|   // check whether appUrl(ROOT_URL) is correct, if not, show an error message
 |   // check whether appUrl(ROOT_URL) is correct, if not, show an error message
 | ||||||
| @ -21,34 +21,34 @@ export function initAdminCommon() { | |||||||
| 
 | 
 | ||||||
|   // New user
 |   // New user
 | ||||||
|   if ($('.admin.new.user').length > 0 || $('.admin.edit.user').length > 0) { |   if ($('.admin.new.user').length > 0 || $('.admin.edit.user').length > 0) { | ||||||
|     document.querySelector('#login_type')?.addEventListener('change', function () { |     document.querySelector<HTMLInputElement>('#login_type')?.addEventListener('change', function () { | ||||||
|       if (this.value?.substring(0, 1) === '0') { |       if (this.value?.startsWith('0')) { | ||||||
|         document.querySelector('#user_name')?.removeAttribute('disabled'); |         document.querySelector<HTMLInputElement>('#user_name')?.removeAttribute('disabled'); | ||||||
|         document.querySelector('#login_name')?.removeAttribute('required'); |         document.querySelector<HTMLInputElement>('#login_name')?.removeAttribute('required'); | ||||||
|         hideElem('.non-local'); |         hideElem('.non-local'); | ||||||
|         showElem('.local'); |         showElem('.local'); | ||||||
|         document.querySelector('#user_name')?.focus(); |         document.querySelector<HTMLInputElement>('#user_name')?.focus(); | ||||||
| 
 | 
 | ||||||
|         if (this.getAttribute('data-password') === 'required') { |         if (this.getAttribute('data-password') === 'required') { | ||||||
|           document.querySelector('#password')?.setAttribute('required', 'required'); |           document.querySelector('#password')?.setAttribute('required', 'required'); | ||||||
|         } |         } | ||||||
|       } else { |       } else { | ||||||
|         if (document.querySelector('.admin.edit.user')) { |         if (document.querySelector<HTMLDivElement>('.admin.edit.user')) { | ||||||
|           document.querySelector('#user_name')?.setAttribute('disabled', 'disabled'); |           document.querySelector<HTMLInputElement>('#user_name')?.setAttribute('disabled', 'disabled'); | ||||||
|         } |         } | ||||||
|         document.querySelector('#login_name')?.setAttribute('required', 'required'); |         document.querySelector<HTMLInputElement>('#login_name')?.setAttribute('required', 'required'); | ||||||
|         showElem('.non-local'); |         showElem('.non-local'); | ||||||
|         hideElem('.local'); |         hideElem('.local'); | ||||||
|         document.querySelector('#login_name')?.focus(); |         document.querySelector<HTMLInputElement>('#login_name')?.focus(); | ||||||
| 
 | 
 | ||||||
|         document.querySelector('#password')?.removeAttribute('required'); |         document.querySelector<HTMLInputElement>('#password')?.removeAttribute('required'); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function onUsePagedSearchChange() { |   function onUsePagedSearchChange() { | ||||||
|     const searchPageSizeElements = document.querySelectorAll('.search-page-size'); |     const searchPageSizeElements = document.querySelectorAll<HTMLDivElement>('.search-page-size'); | ||||||
|     if (document.querySelector('#use_paged_search').checked) { |     if (document.querySelector<HTMLInputElement>('#use_paged_search').checked) { | ||||||
|       showElem('.search-page-size'); |       showElem('.search-page-size'); | ||||||
|       for (const el of searchPageSizeElements) { |       for (const el of searchPageSizeElements) { | ||||||
|         el.querySelector('input')?.setAttribute('required', 'required'); |         el.querySelector('input')?.setAttribute('required', 'required'); | ||||||
| @ -61,20 +61,20 @@ export function initAdminCommon() { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function onOAuth2Change(applyDefaultValues) { |   function onOAuth2Change(applyDefaultValues: boolean) { | ||||||
|     hideElem('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url'); |     hideElem('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url'); | ||||||
|     for (const input of document.querySelectorAll('.open_id_connect_auto_discovery_url input[required]')) { |     for (const input of document.querySelectorAll<HTMLInputElement>('.open_id_connect_auto_discovery_url input[required]')) { | ||||||
|       input.removeAttribute('required'); |       input.removeAttribute('required'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const provider = document.querySelector('#oauth2_provider').value; |     const provider = document.querySelector<HTMLInputElement>('#oauth2_provider').value; | ||||||
|     switch (provider) { |     switch (provider) { | ||||||
|       case 'openidConnect': |       case 'openidConnect': | ||||||
|         document.querySelector('.open_id_connect_auto_discovery_url input').setAttribute('required', 'required'); |         document.querySelector<HTMLInputElement>('.open_id_connect_auto_discovery_url input').setAttribute('required', 'required'); | ||||||
|         showElem('.open_id_connect_auto_discovery_url'); |         showElem('.open_id_connect_auto_discovery_url'); | ||||||
|         break; |         break; | ||||||
|       default: { |       default: { | ||||||
|         const elProviderCustomUrlSettings = document.querySelector(`#${provider}_customURLSettings`); |         const elProviderCustomUrlSettings = document.querySelector<HTMLInputElement>(`#${provider}_customURLSettings`); | ||||||
|         if (!elProviderCustomUrlSettings) break; // some providers do not have custom URL settings
 |         if (!elProviderCustomUrlSettings) break; // some providers do not have custom URL settings
 | ||||||
|         const couldChangeCustomURLs = elProviderCustomUrlSettings.getAttribute('data-available') === 'true'; |         const couldChangeCustomURLs = elProviderCustomUrlSettings.getAttribute('data-available') === 'true'; | ||||||
|         const mustProvideCustomURLs = elProviderCustomUrlSettings.getAttribute('data-required') === 'true'; |         const mustProvideCustomURLs = elProviderCustomUrlSettings.getAttribute('data-required') === 'true'; | ||||||
| @ -82,7 +82,7 @@ export function initAdminCommon() { | |||||||
|           showElem('.oauth2_use_custom_url'); // show the checkbox
 |           showElem('.oauth2_use_custom_url'); // show the checkbox
 | ||||||
|         } |         } | ||||||
|         if (mustProvideCustomURLs) { |         if (mustProvideCustomURLs) { | ||||||
|           document.querySelector('#oauth2_use_custom_url').checked = true; // make the checkbox checked
 |           document.querySelector<HTMLInputElement>('#oauth2_use_custom_url').checked = true; // make the checkbox checked
 | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
| @ -91,17 +91,17 @@ export function initAdminCommon() { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function onOAuth2UseCustomURLChange(applyDefaultValues) { |   function onOAuth2UseCustomURLChange(applyDefaultValues) { | ||||||
|     const provider = document.querySelector('#oauth2_provider').value; |     const provider = document.querySelector<HTMLInputElement>('#oauth2_provider').value; | ||||||
|     hideElem('.oauth2_use_custom_url_field'); |     hideElem('.oauth2_use_custom_url_field'); | ||||||
|     for (const input of document.querySelectorAll('.oauth2_use_custom_url_field input[required]')) { |     for (const input of document.querySelectorAll<HTMLInputElement>('.oauth2_use_custom_url_field input[required]')) { | ||||||
|       input.removeAttribute('required'); |       input.removeAttribute('required'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const elProviderCustomUrlSettings = document.querySelector(`#${provider}_customURLSettings`); |     const elProviderCustomUrlSettings = document.querySelector(`#${provider}_customURLSettings`); | ||||||
|     if (elProviderCustomUrlSettings && document.querySelector('#oauth2_use_custom_url').checked) { |     if (elProviderCustomUrlSettings && document.querySelector<HTMLInputElement>('#oauth2_use_custom_url').checked) { | ||||||
|       for (const custom of ['token_url', 'auth_url', 'profile_url', 'email_url', 'tenant']) { |       for (const custom of ['token_url', 'auth_url', 'profile_url', 'email_url', 'tenant']) { | ||||||
|         if (applyDefaultValues) { |         if (applyDefaultValues) { | ||||||
|           document.querySelector(`#oauth2_${custom}`).value = document.querySelector(`#${provider}_${custom}`).value; |           document.querySelector<HTMLInputElement>(`#oauth2_${custom}`).value = document.querySelector<HTMLInputElement>(`#${provider}_${custom}`).value; | ||||||
|         } |         } | ||||||
|         const customInput = document.querySelector(`#${provider}_${custom}`); |         const customInput = document.querySelector(`#${provider}_${custom}`); | ||||||
|         if (customInput && customInput.getAttribute('data-available') === 'true') { |         if (customInput && customInput.getAttribute('data-available') === 'true') { | ||||||
| @ -115,25 +115,26 @@ export function initAdminCommon() { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function onEnableLdapGroupsChange() { |   function onEnableLdapGroupsChange() { | ||||||
|     toggleElem(document.querySelector('#ldap-group-options'), $('.js-ldap-group-toggle')[0].checked); |     const checked = document.querySelector<HTMLInputElement>('.js-ldap-group-toggle')?.checked; | ||||||
|  |     toggleElem(document.querySelector('#ldap-group-options'), checked); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // New authentication
 |   // New authentication
 | ||||||
|   if (document.querySelector('.admin.new.authentication')) { |   if (document.querySelector<HTMLDivElement>('.admin.new.authentication')) { | ||||||
|     document.querySelector('#auth_type')?.addEventListener('change', function () { |     document.querySelector<HTMLInputElement>('#auth_type')?.addEventListener('change', function () { | ||||||
|       hideElem('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi'); |       hideElem('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi'); | ||||||
| 
 | 
 | ||||||
|       for (const input of document.querySelectorAll('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required], .sspi input[required]')) { |       for (const input of document.querySelectorAll<HTMLInputElement>('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required], .sspi input[required]')) { | ||||||
|         input.removeAttribute('required'); |         input.removeAttribute('required'); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       document.querySelector('.binddnrequired')?.classList.remove('required'); |       document.querySelector<HTMLDivElement>('.binddnrequired')?.classList.remove('required'); | ||||||
| 
 | 
 | ||||||
|       const authType = this.value; |       const authType = this.value; | ||||||
|       switch (authType) { |       switch (authType) { | ||||||
|         case '2': // LDAP
 |         case '2': // LDAP
 | ||||||
|           showElem('.ldap'); |           showElem('.ldap'); | ||||||
|           for (const input of document.querySelectorAll('.binddnrequired input, .ldap div.required:not(.dldap) input')) { |           for (const input of document.querySelectorAll<HTMLInputElement>('.binddnrequired input, .ldap div.required:not(.dldap) input')) { | ||||||
|             input.setAttribute('required', 'required'); |             input.setAttribute('required', 'required'); | ||||||
|           } |           } | ||||||
|           document.querySelector('.binddnrequired')?.classList.add('required'); |           document.querySelector('.binddnrequired')?.classList.add('required'); | ||||||
| @ -141,32 +142,32 @@ export function initAdminCommon() { | |||||||
|         case '3': // SMTP
 |         case '3': // SMTP
 | ||||||
|           showElem('.smtp'); |           showElem('.smtp'); | ||||||
|           showElem('.has-tls'); |           showElem('.has-tls'); | ||||||
|           for (const input of document.querySelectorAll('.smtp div.required input, .has-tls')) { |           for (const input of document.querySelectorAll<HTMLInputElement>('.smtp div.required input, .has-tls')) { | ||||||
|             input.setAttribute('required', 'required'); |             input.setAttribute('required', 'required'); | ||||||
|           } |           } | ||||||
|           break; |           break; | ||||||
|         case '4': // PAM
 |         case '4': // PAM
 | ||||||
|           showElem('.pam'); |           showElem('.pam'); | ||||||
|           for (const input of document.querySelectorAll('.pam input')) { |           for (const input of document.querySelectorAll<HTMLInputElement>('.pam input')) { | ||||||
|             input.setAttribute('required', 'required'); |             input.setAttribute('required', 'required'); | ||||||
|           } |           } | ||||||
|           break; |           break; | ||||||
|         case '5': // LDAP
 |         case '5': // LDAP
 | ||||||
|           showElem('.dldap'); |           showElem('.dldap'); | ||||||
|           for (const input of document.querySelectorAll('.dldap div.required:not(.ldap) input')) { |           for (const input of document.querySelectorAll<HTMLInputElement>('.dldap div.required:not(.ldap) input')) { | ||||||
|             input.setAttribute('required', 'required'); |             input.setAttribute('required', 'required'); | ||||||
|           } |           } | ||||||
|           break; |           break; | ||||||
|         case '6': // OAuth2
 |         case '6': // OAuth2
 | ||||||
|           showElem('.oauth2'); |           showElem('.oauth2'); | ||||||
|           for (const input of document.querySelectorAll('.oauth2 div.required:not(.oauth2_use_custom_url,.oauth2_use_custom_url_field,.open_id_connect_auto_discovery_url) input')) { |           for (const input of document.querySelectorAll<HTMLInputElement>('.oauth2 div.required:not(.oauth2_use_custom_url,.oauth2_use_custom_url_field,.open_id_connect_auto_discovery_url) input')) { | ||||||
|             input.setAttribute('required', 'required'); |             input.setAttribute('required', 'required'); | ||||||
|           } |           } | ||||||
|           onOAuth2Change(true); |           onOAuth2Change(true); | ||||||
|           break; |           break; | ||||||
|         case '7': // SSPI
 |         case '7': // SSPI
 | ||||||
|           showElem('.sspi'); |           showElem('.sspi'); | ||||||
|           for (const input of document.querySelectorAll('.sspi div.required input')) { |           for (const input of document.querySelectorAll<HTMLInputElement>('.sspi div.required input')) { | ||||||
|             input.setAttribute('required', 'required'); |             input.setAttribute('required', 'required'); | ||||||
|           } |           } | ||||||
|           break; |           break; | ||||||
| @ -180,39 +181,39 @@ export function initAdminCommon() { | |||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|     $('#auth_type').trigger('change'); |     $('#auth_type').trigger('change'); | ||||||
|     document.querySelector('#security_protocol')?.addEventListener('change', onSecurityProtocolChange); |     document.querySelector<HTMLInputElement>('#security_protocol')?.addEventListener('change', onSecurityProtocolChange); | ||||||
|     document.querySelector('#use_paged_search')?.addEventListener('change', onUsePagedSearchChange); |     document.querySelector<HTMLInputElement>('#use_paged_search')?.addEventListener('change', onUsePagedSearchChange); | ||||||
|     document.querySelector('#oauth2_provider')?.addEventListener('change', () => onOAuth2Change(true)); |     document.querySelector<HTMLInputElement>('#oauth2_provider')?.addEventListener('change', () => onOAuth2Change(true)); | ||||||
|     document.querySelector('#oauth2_use_custom_url')?.addEventListener('change', () => onOAuth2UseCustomURLChange(true)); |     document.querySelector<HTMLInputElement>('#oauth2_use_custom_url')?.addEventListener('change', () => onOAuth2UseCustomURLChange(true)); | ||||||
|     $('.js-ldap-group-toggle').on('change', onEnableLdapGroupsChange); |     $('.js-ldap-group-toggle').on('change', onEnableLdapGroupsChange); | ||||||
|   } |   } | ||||||
|   // Edit authentication
 |   // Edit authentication
 | ||||||
|   if (document.querySelector('.admin.edit.authentication')) { |   if (document.querySelector<HTMLDivElement>('.admin.edit.authentication')) { | ||||||
|     const authType = document.querySelector('#auth_type')?.value; |     const authType = document.querySelector<HTMLInputElement>('#auth_type')?.value; | ||||||
|     if (authType === '2' || authType === '5') { |     if (authType === '2' || authType === '5') { | ||||||
|       document.querySelector('#security_protocol')?.addEventListener('change', onSecurityProtocolChange); |       document.querySelector<HTMLInputElement>('#security_protocol')?.addEventListener('change', onSecurityProtocolChange); | ||||||
|       $('.js-ldap-group-toggle').on('change', onEnableLdapGroupsChange); |       $('.js-ldap-group-toggle').on('change', onEnableLdapGroupsChange); | ||||||
|       onEnableLdapGroupsChange(); |       onEnableLdapGroupsChange(); | ||||||
|       if (authType === '2') { |       if (authType === '2') { | ||||||
|         document.querySelector('#use_paged_search')?.addEventListener('change', onUsePagedSearchChange); |         document.querySelector<HTMLInputElement>('#use_paged_search')?.addEventListener('change', onUsePagedSearchChange); | ||||||
|       } |       } | ||||||
|     } else if (authType === '6') { |     } else if (authType === '6') { | ||||||
|       document.querySelector('#oauth2_provider')?.addEventListener('change', () => onOAuth2Change(true)); |       document.querySelector<HTMLInputElement>('#oauth2_provider')?.addEventListener('change', () => onOAuth2Change(true)); | ||||||
|       document.querySelector('#oauth2_use_custom_url')?.addEventListener('change', () => onOAuth2UseCustomURLChange(false)); |       document.querySelector<HTMLInputElement>('#oauth2_use_custom_url')?.addEventListener('change', () => onOAuth2UseCustomURLChange(false)); | ||||||
|       onOAuth2Change(false); |       onOAuth2Change(false); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (document.querySelector('.admin.authentication')) { |   if (document.querySelector<HTMLDivElement>('.admin.authentication')) { | ||||||
|     $('#auth_name').on('input', function () { |     $('#auth_name').on('input', function () { | ||||||
|       // appSubUrl is either empty or is a path that starts with `/` and doesn't have a trailing slash.
 |       // appSubUrl is either empty or is a path that starts with `/` and doesn't have a trailing slash.
 | ||||||
|       document.querySelector('#oauth2-callback-url').textContent = `${window.location.origin}${appSubUrl}/user/oauth2/${encodeURIComponent(this.value)}/callback`; |       document.querySelector('#oauth2-callback-url').textContent = `${window.location.origin}${appSubUrl}/user/oauth2/${encodeURIComponent((this as HTMLInputElement).value)}/callback`; | ||||||
|     }).trigger('input'); |     }).trigger('input'); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // Notice
 |   // Notice
 | ||||||
|   if (document.querySelector('.admin.notice')) { |   if (document.querySelector<HTMLDivElement>('.admin.notice')) { | ||||||
|     const detailModal = document.querySelector('#detail-modal'); |     const detailModal = document.querySelector<HTMLDivElement>('#detail-modal'); | ||||||
| 
 | 
 | ||||||
|     // Attach view detail modals
 |     // Attach view detail modals
 | ||||||
|     $('.view-detail').on('click', function () { |     $('.view-detail').on('click', function () { | ||||||
| @ -223,7 +224,7 @@ export function initAdminCommon() { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // Select actions
 |     // Select actions
 | ||||||
|     const checkboxes = document.querySelectorAll('.select.table .ui.checkbox input'); |     const checkboxes = document.querySelectorAll<HTMLInputElement>('.select.table .ui.checkbox input'); | ||||||
| 
 | 
 | ||||||
|     $('.select.action').on('click', function () { |     $('.select.action').on('click', function () { | ||||||
|       switch ($(this).data('action')) { |       switch ($(this).data('action')) { | ||||||
| @ -244,7 +245,7 @@ export function initAdminCommon() { | |||||||
|           break; |           break; | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|     document.querySelector('#delete-selection')?.addEventListener('click', async function (e) { |     document.querySelector<HTMLButtonElement>('#delete-selection')?.addEventListener('click', async function (e) { | ||||||
|       e.preventDefault(); |       e.preventDefault(); | ||||||
|       this.classList.add('is-loading', 'disabled'); |       this.classList.add('is-loading', 'disabled'); | ||||||
|       const data = new FormData(); |       const data = new FormData(); | ||||||
|  | |||||||
| @ -3,17 +3,17 @@ import {POST} from '../../modules/fetch.ts'; | |||||||
| 
 | 
 | ||||||
| const {appSubUrl} = window.config; | const {appSubUrl} = window.config; | ||||||
| 
 | 
 | ||||||
| export function initAdminConfigs() { | export function initAdminConfigs(): void { | ||||||
|   const elAdminConfig = document.querySelector('.page-content.admin.config'); |   const elAdminConfig = document.querySelector<HTMLDivElement>('.page-content.admin.config'); | ||||||
|   if (!elAdminConfig) return; |   if (!elAdminConfig) return; | ||||||
| 
 | 
 | ||||||
|   for (const el of elAdminConfig.querySelectorAll('input[type="checkbox"][data-config-dyn-key]')) { |   for (const el of elAdminConfig.querySelectorAll<HTMLInputElement>('input[type="checkbox"][data-config-dyn-key]')) { | ||||||
|     el.addEventListener('change', async () => { |     el.addEventListener('change', async () => { | ||||||
|       try { |       try { | ||||||
|         const resp = await POST(`${appSubUrl}/-/admin/config`, { |         const resp = await POST(`${appSubUrl}/-/admin/config`, { | ||||||
|           data: new URLSearchParams({key: el.getAttribute('data-config-dyn-key'), value: el.checked}), |           data: new URLSearchParams({key: el.getAttribute('data-config-dyn-key'), value: String(el.checked)}), | ||||||
|         }); |         }); | ||||||
|         const json = await resp.json(); |         const json: Record<string, any> = await resp.json(); | ||||||
|         if (json.errorMessage) throw new Error(json.errorMessage); |         if (json.errorMessage) throw new Error(json.errorMessage); | ||||||
|       } catch (ex) { |       } catch (ex) { | ||||||
|         showTemporaryTooltip(el, ex.toString()); |         showTemporaryTooltip(el, ex.toString()); | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import $ from 'jquery'; | import $ from 'jquery'; | ||||||
| 
 | 
 | ||||||
| export function initAdminEmails() { | export function initAdminEmails(): void { | ||||||
|   function linkEmailAction(e) { |   $('.link-email-action').on('click', (e) => { | ||||||
|     const $this = $(this); |     const $this = $(this); | ||||||
|     $('#form-uid').val($this.data('uid')); |     $('#form-uid').val($this.data('uid')); | ||||||
|     $('#form-email').val($this.data('email')); |     $('#form-email').val($this.data('email')); | ||||||
| @ -9,6 +9,5 @@ export function initAdminEmails() { | |||||||
|     $('#form-activate').val($this.data('activate')); |     $('#form-activate').val($this.data('activate')); | ||||||
|     $('#change-email-modal').modal('show'); |     $('#change-email-modal').modal('show'); | ||||||
|     e.preventDefault(); |     e.preventDefault(); | ||||||
|   } |   }); | ||||||
|   $('.link-email-action').on('click', linkEmailAction); |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,16 +7,16 @@ export async function initAdminSelfCheck() { | |||||||
|   const elCheckByFrontend = document.querySelector('#self-check-by-frontend'); |   const elCheckByFrontend = document.querySelector('#self-check-by-frontend'); | ||||||
|   if (!elCheckByFrontend) return; |   if (!elCheckByFrontend) return; | ||||||
| 
 | 
 | ||||||
|   const elContent = document.querySelector('.page-content.admin .admin-setting-content'); |   const elContent = document.querySelector<HTMLDivElement>('.page-content.admin .admin-setting-content'); | ||||||
| 
 | 
 | ||||||
|   // send frontend self-check request
 |   // send frontend self-check request
 | ||||||
|   const resp = await POST(`${appSubUrl}/-/admin/self_check`, { |   const resp = await POST(`${appSubUrl}/-/admin/self_check`, { | ||||||
|     data: new URLSearchParams({ |     data: new URLSearchParams({ | ||||||
|       location_origin: window.location.origin, |       location_origin: window.location.origin, | ||||||
|       now: Date.now(), // TODO: check time difference between server and client
 |       now: String(Date.now()), // TODO: check time difference between server and client
 | ||||||
|     }), |     }), | ||||||
|   }); |   }); | ||||||
|   const json = await resp.json(); |   const json: Record<string, any> = await resp.json(); | ||||||
|   toggleElem(elCheckByFrontend, Boolean(json.problems?.length)); |   toggleElem(elCheckByFrontend, Boolean(json.problems?.length)); | ||||||
|   for (const problem of json.problems ?? []) { |   for (const problem of json.problems ?? []) { | ||||||
|     const elProblem = document.createElement('div'); |     const elProblem = document.createElement('div'); | ||||||
|  | |||||||
| @ -1,8 +1,8 @@ | |||||||
| export function initAdminUserListSearchForm() { | export function initAdminUserListSearchForm(): void { | ||||||
|   const searchForm = window.config.pageData.adminUserListSearchForm; |   const searchForm = window.config.pageData.adminUserListSearchForm; | ||||||
|   if (!searchForm) return; |   if (!searchForm) return; | ||||||
| 
 | 
 | ||||||
|   const form = document.querySelector('#user-list-search-form'); |   const form = document.querySelector<HTMLFormElement>('#user-list-search-form'); | ||||||
|   if (!form) return; |   if (!form) return; | ||||||
| 
 | 
 | ||||||
|   for (const button of form.querySelectorAll(`button[name=sort][value="${searchForm.SortType}"]`)) { |   for (const button of form.querySelectorAll(`button[name=sort][value="${searchForm.SortType}"]`)) { | ||||||
| @ -12,23 +12,23 @@ export function initAdminUserListSearchForm() { | |||||||
|   if (searchForm.StatusFilterMap) { |   if (searchForm.StatusFilterMap) { | ||||||
|     for (const [k, v] of Object.entries(searchForm.StatusFilterMap)) { |     for (const [k, v] of Object.entries(searchForm.StatusFilterMap)) { | ||||||
|       if (!v) continue; |       if (!v) continue; | ||||||
|       for (const input of form.querySelectorAll(`input[name="status_filter[${k}]"][value="${v}"]`)) { |       for (const input of form.querySelectorAll<HTMLInputElement>(`input[name="status_filter[${k}]"][value="${v}"]`)) { | ||||||
|         input.checked = true; |         input.checked = true; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   for (const radio of form.querySelectorAll('input[type=radio]')) { |   for (const radio of form.querySelectorAll<HTMLInputElement>('input[type=radio]')) { | ||||||
|     radio.addEventListener('click', () => { |     radio.addEventListener('click', () => { | ||||||
|       form.submit(); |       form.submit(); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const resetButtons = form.querySelectorAll('.j-reset-status-filter'); |   const resetButtons = form.querySelectorAll<HTMLAnchorElement>('.j-reset-status-filter'); | ||||||
|   for (const button of resetButtons) { |   for (const button of resetButtons) { | ||||||
|     button.addEventListener('click', (e) => { |     button.addEventListener('click', (e) => { | ||||||
|       e.preventDefault(); |       e.preventDefault(); | ||||||
|       for (const input of form.querySelectorAll('input[type=radio]')) { |       for (const input of form.querySelectorAll<HTMLInputElement>('input[type=radio]')) { | ||||||
|         if (input.name.startsWith('status_filter[')) { |         if (input.name.startsWith('status_filter[')) { | ||||||
|           input.checked = false; |           input.checked = false; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,11 +1,11 @@ | |||||||
| import {svg} from '../svg.ts'; | import {svg} from '../svg.ts'; | ||||||
| 
 | 
 | ||||||
| const addPrefix = (str) => `user-content-${str}`; | const addPrefix = (str: string): string => `user-content-${str}`; | ||||||
| const removePrefix = (str) => str.replace(/^user-content-/, ''); | const removePrefix = (str: string): string => str.replace(/^user-content-/, ''); | ||||||
| const hasPrefix = (str) => str.startsWith('user-content-'); | const hasPrefix = (str: string): boolean => str.startsWith('user-content-'); | ||||||
| 
 | 
 | ||||||
| // scroll to anchor while respecting the `user-content` prefix that exists on the target
 | // scroll to anchor while respecting the `user-content` prefix that exists on the target
 | ||||||
| function scrollToAnchor(encodedId) { | function scrollToAnchor(encodedId: string): void { | ||||||
|   if (!encodedId) return; |   if (!encodedId) return; | ||||||
|   const id = decodeURIComponent(encodedId); |   const id = decodeURIComponent(encodedId); | ||||||
|   const prefixedId = addPrefix(id); |   const prefixedId = addPrefix(id); | ||||||
| @ -24,7 +24,7 @@ function scrollToAnchor(encodedId) { | |||||||
|   el?.scrollIntoView(); |   el?.scrollIntoView(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function initMarkupAnchors() { | export function initMarkupAnchors(): void { | ||||||
|   const markupEls = document.querySelectorAll('.markup'); |   const markupEls = document.querySelectorAll('.markup'); | ||||||
|   if (!markupEls.length) return; |   if (!markupEls.length) return; | ||||||
| 
 | 
 | ||||||
| @ -39,7 +39,7 @@ export function initMarkupAnchors() { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // remove `user-content-` prefix from links so they don't show in url bar when clicked
 |     // remove `user-content-` prefix from links so they don't show in url bar when clicked
 | ||||||
|     for (const a of markupEl.querySelectorAll('a[href^="#"]')) { |     for (const a of markupEl.querySelectorAll<HTMLAnchorElement>('a[href^="#"]')) { | ||||||
|       const href = a.getAttribute('href'); |       const href = a.getAttribute('href'); | ||||||
|       if (!href.startsWith('#user-content-')) continue; |       if (!href.startsWith('#user-content-')) continue; | ||||||
|       a.setAttribute('href', `#${removePrefix(href.substring(1))}`); |       a.setAttribute('href', `#${removePrefix(href.substring(1))}`); | ||||||
| @ -47,15 +47,15 @@ export function initMarkupAnchors() { | |||||||
| 
 | 
 | ||||||
|     // add `user-content-` prefix to user-generated `a[name]` link targets
 |     // add `user-content-` prefix to user-generated `a[name]` link targets
 | ||||||
|     // TODO: this prefix should be added in backend instead
 |     // TODO: this prefix should be added in backend instead
 | ||||||
|     for (const a of markupEl.querySelectorAll('a[name]')) { |     for (const a of markupEl.querySelectorAll<HTMLAnchorElement>('a[name]')) { | ||||||
|       const name = a.getAttribute('name'); |       const name = a.getAttribute('name'); | ||||||
|       if (!name) continue; |       if (!name) continue; | ||||||
|       a.setAttribute('name', addPrefix(a.name)); |       a.setAttribute('name', addPrefix(name)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for (const a of markupEl.querySelectorAll('a[href^="#"]')) { |     for (const a of markupEl.querySelectorAll<HTMLAnchorElement>('a[href^="#"]')) { | ||||||
|       a.addEventListener('click', (e) => { |       a.addEventListener('click', (e) => { | ||||||
|         scrollToAnchor(e.currentTarget.getAttribute('href')?.substring(1)); |         scrollToAnchor((e.currentTarget as HTMLAnchorElement).getAttribute('href')?.substring(1)); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -1,13 +1,13 @@ | |||||||
| import {svg} from '../svg.ts'; | import {svg} from '../svg.ts'; | ||||||
| 
 | 
 | ||||||
| export function makeCodeCopyButton() { | export function makeCodeCopyButton(): HTMLButtonElement { | ||||||
|   const button = document.createElement('button'); |   const button = document.createElement('button'); | ||||||
|   button.classList.add('code-copy', 'ui', 'button'); |   button.classList.add('code-copy', 'ui', 'button'); | ||||||
|   button.innerHTML = svg('octicon-copy'); |   button.innerHTML = svg('octicon-copy'); | ||||||
|   return button; |   return button; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function renderCodeCopy() { | export function renderCodeCopy(): void { | ||||||
|   const els = document.querySelectorAll('.markup .code-block code'); |   const els = document.querySelectorAll('.markup .code-block code'); | ||||||
|   if (!els.length) return; |   if (!els.length) return; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,8 +1,8 @@ | |||||||
| export function displayError(el, err) { | export function displayError(el: Element, err: Error): void { | ||||||
|   el.classList.remove('is-loading'); |   el.classList.remove('is-loading'); | ||||||
|   const errorNode = document.createElement('pre'); |   const errorNode = document.createElement('pre'); | ||||||
|   errorNode.setAttribute('class', 'ui message error markup-block-error'); |   errorNode.setAttribute('class', 'ui message error markup-block-error'); | ||||||
|   errorNode.textContent = err.str || err.message || String(err); |   errorNode.textContent = err.message || String(err); | ||||||
|   el.before(errorNode); |   el.before(errorNode); | ||||||
|   el.setAttribute('data-render-done', 'true'); |   el.setAttribute('data-render-done', 'true'); | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ import {renderAsciicast} from './asciicast.ts'; | |||||||
| import {initMarkupTasklist} from './tasklist.ts'; | import {initMarkupTasklist} from './tasklist.ts'; | ||||||
| 
 | 
 | ||||||
| // code that runs for all markup content
 | // code that runs for all markup content
 | ||||||
| export function initMarkupContent() { | export function initMarkupContent(): void { | ||||||
|   renderMermaid(); |   renderMermaid(); | ||||||
|   renderMath(); |   renderMath(); | ||||||
|   renderCodeCopy(); |   renderCodeCopy(); | ||||||
| @ -13,6 +13,6 @@ export function initMarkupContent() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // code that only runs for comments
 | // code that only runs for comments
 | ||||||
| export function initCommentContent() { | export function initCommentContent(): void { | ||||||
|   initMarkupTasklist(); |   initMarkupTasklist(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -12,21 +12,20 @@ type ProcessorContext = { | |||||||
| 
 | 
 | ||||||
| function prepareProcessors(ctx:ProcessorContext): Processors { | function prepareProcessors(ctx:ProcessorContext): Processors { | ||||||
|   const processors = { |   const processors = { | ||||||
|     H1(el) { |     H1(el: HTMLHeadingElement) { | ||||||
|       const level = parseInt(el.tagName.slice(1)); |       const level = parseInt(el.tagName.slice(1)); | ||||||
|       el.textContent = `${'#'.repeat(level)} ${el.textContent.trim()}`; |       el.textContent = `${'#'.repeat(level)} ${el.textContent.trim()}`; | ||||||
|     }, |     }, | ||||||
|     STRONG(el) { |     STRONG(el: HTMLElement) { | ||||||
|       return `**${el.textContent}**`; |       return `**${el.textContent}**`; | ||||||
|     }, |     }, | ||||||
|     EM(el) { |     EM(el: HTMLElement) { | ||||||
|       return `_${el.textContent}_`; |       return `_${el.textContent}_`; | ||||||
|     }, |     }, | ||||||
|     DEL(el) { |     DEL(el: HTMLElement) { | ||||||
|       return `~~${el.textContent}~~`; |       return `~~${el.textContent}~~`; | ||||||
|     }, |     }, | ||||||
| 
 |     A(el: HTMLAnchorElement) { | ||||||
|     A(el) { |  | ||||||
|       const text = el.textContent || 'link'; |       const text = el.textContent || 'link'; | ||||||
|       const href = el.getAttribute('href'); |       const href = el.getAttribute('href'); | ||||||
|       if (/^https?:/.test(text) && text === href) { |       if (/^https?:/.test(text) && text === href) { | ||||||
| @ -34,7 +33,7 @@ function prepareProcessors(ctx:ProcessorContext): Processors { | |||||||
|       } |       } | ||||||
|       return href ? `[${text}](${href})` : text; |       return href ? `[${text}](${href})` : text; | ||||||
|     }, |     }, | ||||||
|     IMG(el) { |     IMG(el: HTMLImageElement) { | ||||||
|       const alt = el.getAttribute('alt') || 'image'; |       const alt = el.getAttribute('alt') || 'image'; | ||||||
|       const src = el.getAttribute('src'); |       const src = el.getAttribute('src'); | ||||||
|       const widthAttr = el.hasAttribute('width') ? ` width="${htmlEscape(el.getAttribute('width') || '')}"` : ''; |       const widthAttr = el.hasAttribute('width') ? ` width="${htmlEscape(el.getAttribute('width') || '')}"` : ''; | ||||||
| @ -44,32 +43,29 @@ function prepareProcessors(ctx:ProcessorContext): Processors { | |||||||
|       } |       } | ||||||
|       return ``; |       return ``; | ||||||
|     }, |     }, | ||||||
| 
 |     P(el: HTMLParagraphElement) { | ||||||
|     P(el) { |  | ||||||
|       el.textContent = `${el.textContent}\n`; |       el.textContent = `${el.textContent}\n`; | ||||||
|     }, |     }, | ||||||
|     BLOCKQUOTE(el) { |     BLOCKQUOTE(el: HTMLElement) { | ||||||
|       el.textContent = `${el.textContent.replace(/^/mg, '> ')}\n`; |       el.textContent = `${el.textContent.replace(/^/mg, '> ')}\n`; | ||||||
|     }, |     }, | ||||||
| 
 |     OL(el: HTMLElement) { | ||||||
|     OL(el) { |  | ||||||
|       const preNewLine = ctx.listNestingLevel ? '\n' : ''; |       const preNewLine = ctx.listNestingLevel ? '\n' : ''; | ||||||
|       el.textContent = `${preNewLine}${el.textContent}\n`; |       el.textContent = `${preNewLine}${el.textContent}\n`; | ||||||
|     }, |     }, | ||||||
|     LI(el) { |     LI(el: HTMLElement) { | ||||||
|       const parent = el.parentNode; |       const parent = el.parentNode; | ||||||
|       const bullet = parent.tagName === 'OL' ? `1. ` : '* '; |       const bullet = (parent as HTMLElement).tagName === 'OL' ? `1. ` : '* '; | ||||||
|       const nestingIdentLevel = Math.max(0, ctx.listNestingLevel - 1); |       const nestingIdentLevel = Math.max(0, ctx.listNestingLevel - 1); | ||||||
|       el.textContent = `${' '.repeat(nestingIdentLevel * 4)}${bullet}${el.textContent}${ctx.elementIsLast ? '' : '\n'}`; |       el.textContent = `${' '.repeat(nestingIdentLevel * 4)}${bullet}${el.textContent}${ctx.elementIsLast ? '' : '\n'}`; | ||||||
|       return el; |       return el; | ||||||
|     }, |     }, | ||||||
|     INPUT(el) { |     INPUT(el: HTMLInputElement) { | ||||||
|       return el.checked ? '[x] ' : '[ ] '; |       return el.checked ? '[x] ' : '[ ] '; | ||||||
|     }, |     }, | ||||||
| 
 |     CODE(el: HTMLElement) { | ||||||
|     CODE(el) { |  | ||||||
|       const text = el.textContent; |       const text = el.textContent; | ||||||
|       if (el.parentNode && el.parentNode.tagName === 'PRE') { |       if (el.parentNode && (el.parentNode as HTMLElement).tagName === 'PRE') { | ||||||
|         el.textContent = `\`\`\`\n${text}\n\`\`\`\n`; |         el.textContent = `\`\`\`\n${text}\n\`\`\`\n`; | ||||||
|         return el; |         return el; | ||||||
|       } |       } | ||||||
| @ -86,7 +82,7 @@ function prepareProcessors(ctx:ProcessorContext): Processors { | |||||||
|   return processors; |   return processors; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function processElement(ctx :ProcessorContext, processors: Processors, el: HTMLElement) { | function processElement(ctx :ProcessorContext, processors: Processors, el: HTMLElement): string | void { | ||||||
|   if (el.hasAttribute('data-markdown-generated-content')) return el.textContent; |   if (el.hasAttribute('data-markdown-generated-content')) return el.textContent; | ||||||
|   if (el.tagName === 'A' && el.children.length === 1 && el.children[0].tagName === 'IMG') { |   if (el.tagName === 'A' && el.children.length === 1 && el.children[0].tagName === 'IMG') { | ||||||
|     return processElement(ctx, processors, el.children[0] as HTMLElement); |     return processElement(ctx, processors, el.children[0] as HTMLElement); | ||||||
|  | |||||||
| @ -1,12 +1,12 @@ | |||||||
| import {displayError} from './common.ts'; | import {displayError} from './common.ts'; | ||||||
| 
 | 
 | ||||||
| function targetElement(el) { | function targetElement(el: Element) { | ||||||
|   // The target element is either the current element if it has the
 |   // The target element is either the current element if it has the
 | ||||||
|   // `is-loading` class or the pre that contains it
 |   // `is-loading` class or the pre that contains it
 | ||||||
|   return el.classList.contains('is-loading') ? el : el.closest('pre'); |   return el.classList.contains('is-loading') ? el : el.closest('pre'); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function renderMath() { | export async function renderMath(): void { | ||||||
|   const els = document.querySelectorAll('.markup code.language-math'); |   const els = document.querySelectorAll('.markup code.language-math'); | ||||||
|   if (!els.length) return; |   if (!els.length) return; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ body {margin: 0; padding: 0; overflow: hidden} | |||||||
| #mermaid {display: block; margin: 0 auto} | #mermaid {display: block; margin: 0 auto} | ||||||
| blockquote, dd, dl, figure, h1, h2, h3, h4, h5, h6, hr, p, pre {margin: 0}`;
 | blockquote, dd, dl, figure, h1, h2, h3, h4, h5, h6, hr, p, pre {margin: 0}`;
 | ||||||
| 
 | 
 | ||||||
| export async function renderMermaid() { | export async function renderMermaid(): Promise<void> { | ||||||
|   const els = document.querySelectorAll('.markup code.language-mermaid'); |   const els = document.querySelectorAll('.markup code.language-mermaid'); | ||||||
|   if (!els.length) return; |   if (!els.length) return; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import {POST} from '../modules/fetch.ts'; | import {POST} from '../modules/fetch.ts'; | ||||||
| import {showErrorToast} from '../modules/toast.ts'; | import {showErrorToast} from '../modules/toast.ts'; | ||||||
| 
 | 
 | ||||||
| const preventListener = (e) => e.preventDefault(); | const preventListener = (e: Event) => e.preventDefault(); | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Attaches `input` handlers to markdown rendered tasklist checkboxes in comments. |  * Attaches `input` handlers to markdown rendered tasklist checkboxes in comments. | ||||||
| @ -10,10 +10,10 @@ const preventListener = (e) => e.preventDefault(); | |||||||
|  * is set accordingly and sent to the server. On success it updates the raw-content on |  * is set accordingly and sent to the server. On success it updates the raw-content on | ||||||
|  * error it resets the checkbox to its original value. |  * error it resets the checkbox to its original value. | ||||||
|  */ |  */ | ||||||
| export function initMarkupTasklist() { | export function initMarkupTasklist(): void { | ||||||
|   for (const el of document.querySelectorAll(`.markup[data-can-edit=true]`) || []) { |   for (const el of document.querySelectorAll(`.markup[data-can-edit=true]`) || []) { | ||||||
|     const container = el.parentNode; |     const container = el.parentNode; | ||||||
|     const checkboxes = el.querySelectorAll(`.task-list-item input[type=checkbox]`); |     const checkboxes = el.querySelectorAll<HTMLInputElement>(`.task-list-item input[type=checkbox]`); | ||||||
| 
 | 
 | ||||||
|     for (const checkbox of checkboxes) { |     for (const checkbox of checkboxes) { | ||||||
|       if (checkbox.hasAttribute('data-editable')) { |       if (checkbox.hasAttribute('data-editable')) { | ||||||
| @ -52,7 +52,7 @@ export function initMarkupTasklist() { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|           const editContentZone = container.querySelector('.edit-content-zone'); |           const editContentZone = container.querySelector<HTMLDivElement>('.edit-content-zone'); | ||||||
|           const updateUrl = editContentZone.getAttribute('data-update-url'); |           const updateUrl = editContentZone.getAttribute('data-update-url'); | ||||||
|           const context = editContentZone.getAttribute('data-context'); |           const context = editContentZone.getAttribute('data-context'); | ||||||
|           const contentVersion = editContentZone.getAttribute('data-content-version'); |           const contentVersion = editContentZone.getAttribute('data-content-version'); | ||||||
|  | |||||||
| @ -153,7 +153,7 @@ export type SvgName = keyof typeof svgs; | |||||||
| //  most of the SVG icons in assets couldn't be used directly.
 | //  most of the SVG icons in assets couldn't be used directly.
 | ||||||
| 
 | 
 | ||||||
| // retrieve an HTML string for given SVG icon name, size and additional classes
 | // retrieve an HTML string for given SVG icon name, size and additional classes
 | ||||||
| export function svg(name: SvgName, size = 16, classNames: string|string[]): string { | export function svg(name: SvgName, size = 16, classNames?: string|string[]): string { | ||||||
|   const className = Array.isArray(classNames) ? classNames.join(' ') : classNames; |   const className = Array.isArray(classNames) ? classNames.join(' ') : classNames; | ||||||
|   if (!(name in svgs)) throw new Error(`Unknown SVG icon: ${name}`); |   if (!(name in svgs)) throw new Error(`Unknown SVG icon: ${name}`); | ||||||
|   if (size === 16 && !className) return svgs[name]; |   if (size === 16 && !className) return svgs[name]; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user