369 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			369 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import './public-path';
 | |
| import { createRoot } from 'react-dom/client';
 | |
| 
 | |
| import Rails from '@rails/ujs';
 | |
| 
 | |
| import ready from '../mastodon/ready';
 | |
| 
 | |
| const setAnnouncementEndsAttributes = (target: HTMLInputElement) => {
 | |
|   const valid = target.value && target.validity.valid;
 | |
|   const element = document.querySelector<HTMLInputElement>(
 | |
|     'input[type="datetime-local"]#announcement_ends_at',
 | |
|   );
 | |
| 
 | |
|   if (!element) return;
 | |
| 
 | |
|   if (valid) {
 | |
|     element.classList.remove('optional');
 | |
|     element.required = true;
 | |
|     element.min = target.value;
 | |
|   } else {
 | |
|     element.classList.add('optional');
 | |
|     element.removeAttribute('required');
 | |
|     element.removeAttribute('min');
 | |
|   }
 | |
| };
 | |
| 
 | |
| Rails.delegate(
 | |
|   document,
 | |
|   'input[type="datetime-local"]#announcement_starts_at',
 | |
|   'change',
 | |
|   ({ target }) => {
 | |
|     if (target instanceof HTMLInputElement)
 | |
|       setAnnouncementEndsAttributes(target);
 | |
|   },
 | |
| );
 | |
| 
 | |
| const batchCheckboxClassName = '.batch-checkbox input[type="checkbox"]';
 | |
| 
 | |
| const showSelectAll = () => {
 | |
|   const selectAllMatchingElement = document.querySelector(
 | |
|     '.batch-table__select-all',
 | |
|   );
 | |
|   selectAllMatchingElement?.classList.add('active');
 | |
| };
 | |
| 
 | |
| const hideSelectAll = () => {
 | |
|   const selectAllMatchingElement = document.querySelector(
 | |
|     '.batch-table__select-all',
 | |
|   );
 | |
|   const hiddenField = document.querySelector<HTMLInputElement>(
 | |
|     'input#select_all_matching',
 | |
|   );
 | |
|   const selectedMsg = document.querySelector(
 | |
|     '.batch-table__select-all .selected',
 | |
|   );
 | |
|   const notSelectedMsg = document.querySelector(
 | |
|     '.batch-table__select-all .not-selected',
 | |
|   );
 | |
| 
 | |
|   selectAllMatchingElement?.classList.remove('active');
 | |
|   selectedMsg?.classList.remove('active');
 | |
|   notSelectedMsg?.classList.add('active');
 | |
|   if (hiddenField) hiddenField.value = '0';
 | |
| };
 | |
| 
 | |
| Rails.delegate(document, '#batch_checkbox_all', 'change', ({ target }) => {
 | |
|   if (!(target instanceof HTMLInputElement)) return;
 | |
| 
 | |
|   const selectAllMatchingElement = document.querySelector(
 | |
|     '.batch-table__select-all',
 | |
|   );
 | |
| 
 | |
|   document
 | |
|     .querySelectorAll<HTMLInputElement>(batchCheckboxClassName)
 | |
|     .forEach((content) => {
 | |
|       content.checked = target.checked;
 | |
|     });
 | |
| 
 | |
|   if (selectAllMatchingElement) {
 | |
|     if (target.checked) {
 | |
|       showSelectAll();
 | |
|     } else {
 | |
|       hideSelectAll();
 | |
|     }
 | |
|   }
 | |
| });
 | |
| 
 | |
| Rails.delegate(document, '.batch-table__select-all button', 'click', () => {
 | |
|   const hiddenField = document.querySelector<HTMLInputElement>(
 | |
|     '#select_all_matching',
 | |
|   );
 | |
| 
 | |
|   if (!hiddenField) return;
 | |
| 
 | |
|   const active = hiddenField.value === '1';
 | |
|   const selectedMsg = document.querySelector(
 | |
|     '.batch-table__select-all .selected',
 | |
|   );
 | |
|   const notSelectedMsg = document.querySelector(
 | |
|     '.batch-table__select-all .not-selected',
 | |
|   );
 | |
| 
 | |
|   if (!selectedMsg || !notSelectedMsg) return;
 | |
| 
 | |
|   if (active) {
 | |
|     hiddenField.value = '0';
 | |
|     selectedMsg.classList.remove('active');
 | |
|     notSelectedMsg.classList.add('active');
 | |
|   } else {
 | |
|     hiddenField.value = '1';
 | |
|     notSelectedMsg.classList.remove('active');
 | |
|     selectedMsg.classList.add('active');
 | |
|   }
 | |
| });
 | |
| 
 | |
| Rails.delegate(document, batchCheckboxClassName, 'change', () => {
 | |
|   const checkAllElement = document.querySelector<HTMLInputElement>(
 | |
|     'input#batch_checkbox_all',
 | |
|   );
 | |
|   const selectAllMatchingElement = document.querySelector(
 | |
|     '.batch-table__select-all',
 | |
|   );
 | |
| 
 | |
|   if (checkAllElement) {
 | |
|     const allCheckboxes = Array.from(
 | |
|       document.querySelectorAll<HTMLInputElement>(batchCheckboxClassName),
 | |
|     );
 | |
|     checkAllElement.checked = allCheckboxes.every((content) => content.checked);
 | |
|     checkAllElement.indeterminate =
 | |
|       !checkAllElement.checked &&
 | |
|       allCheckboxes.some((content) => content.checked);
 | |
| 
 | |
|     if (selectAllMatchingElement) {
 | |
|       if (checkAllElement.checked) {
 | |
|         showSelectAll();
 | |
|       } else {
 | |
|         hideSelectAll();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| });
 | |
| 
 | |
| Rails.delegate(
 | |
|   document,
 | |
|   '.filter-subset--with-select select',
 | |
|   'change',
 | |
|   ({ target }) => {
 | |
|     if (target instanceof HTMLSelectElement) target.form?.submit();
 | |
|   },
 | |
| );
 | |
| 
 | |
| const onDomainBlockSeverityChange = (target: HTMLSelectElement) => {
 | |
|   const rejectMediaDiv = document.querySelector(
 | |
|     '.input.with_label.domain_block_reject_media',
 | |
|   );
 | |
|   const rejectReportsDiv = document.querySelector(
 | |
|     '.input.with_label.domain_block_reject_reports',
 | |
|   );
 | |
| 
 | |
|   if (rejectMediaDiv && rejectMediaDiv instanceof HTMLElement) {
 | |
|     rejectMediaDiv.style.display =
 | |
|       target.value === 'suspend' ? 'none' : 'block';
 | |
|   }
 | |
| 
 | |
|   if (rejectReportsDiv && rejectReportsDiv instanceof HTMLElement) {
 | |
|     rejectReportsDiv.style.display =
 | |
|       target.value === 'suspend' ? 'none' : 'block';
 | |
|   }
 | |
| };
 | |
| 
 | |
| Rails.delegate(document, '#domain_block_severity', 'change', ({ target }) => {
 | |
|   if (target instanceof HTMLSelectElement) onDomainBlockSeverityChange(target);
 | |
| });
 | |
| 
 | |
| const onEnableBootstrapTimelineAccountsChange = (target: HTMLInputElement) => {
 | |
|   const bootstrapTimelineAccountsField =
 | |
|     document.querySelector<HTMLInputElement>(
 | |
|       '#form_admin_settings_bootstrap_timeline_accounts',
 | |
|     );
 | |
| 
 | |
|   if (bootstrapTimelineAccountsField) {
 | |
|     bootstrapTimelineAccountsField.disabled = !target.checked;
 | |
|     if (target.checked) {
 | |
|       bootstrapTimelineAccountsField.parentElement?.classList.remove(
 | |
|         'disabled',
 | |
|       );
 | |
|       bootstrapTimelineAccountsField.parentElement?.parentElement?.classList.remove(
 | |
|         'disabled',
 | |
|       );
 | |
|     } else {
 | |
|       bootstrapTimelineAccountsField.parentElement?.classList.add('disabled');
 | |
|       bootstrapTimelineAccountsField.parentElement?.parentElement?.classList.add(
 | |
|         'disabled',
 | |
|       );
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| Rails.delegate(
 | |
|   document,
 | |
|   '#form_admin_settings_enable_bootstrap_timeline_accounts',
 | |
|   'change',
 | |
|   ({ target }) => {
 | |
|     if (target instanceof HTMLInputElement)
 | |
|       onEnableBootstrapTimelineAccountsChange(target);
 | |
|   },
 | |
| );
 | |
| 
 | |
| const onChangeRegistrationMode = (target: HTMLSelectElement) => {
 | |
|   const enabled = target.value === 'approved';
 | |
| 
 | |
|   document
 | |
|     .querySelectorAll<HTMLElement>(
 | |
|       '.form_admin_settings_registrations_mode .warning-hint',
 | |
|     )
 | |
|     .forEach((warning_hint) => {
 | |
|       warning_hint.style.display = target.value === 'open' ? 'inline' : 'none';
 | |
|     });
 | |
| 
 | |
|   document
 | |
|     .querySelectorAll<HTMLInputElement>(
 | |
|       'input#form_admin_settings_require_invite_text',
 | |
|     )
 | |
|     .forEach((input) => {
 | |
|       input.disabled = !enabled;
 | |
|       if (enabled) {
 | |
|         let element: HTMLElement | null = input;
 | |
|         do {
 | |
|           element.classList.remove('disabled');
 | |
|           element = element.parentElement;
 | |
|         } while (element && !element.classList.contains('fields-group'));
 | |
|       } else {
 | |
|         let element: HTMLElement | null = input;
 | |
|         do {
 | |
|           element.classList.add('disabled');
 | |
|           element = element.parentElement;
 | |
|         } while (element && !element.classList.contains('fields-group'));
 | |
|       }
 | |
|     });
 | |
| };
 | |
| 
 | |
| const convertUTCDateTimeToLocal = (value: string) => {
 | |
|   const date = new Date(value + 'Z');
 | |
|   const twoChars = (x: number) => x.toString().padStart(2, '0');
 | |
|   return `${date.getFullYear()}-${twoChars(date.getMonth() + 1)}-${twoChars(date.getDate())}T${twoChars(date.getHours())}:${twoChars(date.getMinutes())}`;
 | |
| };
 | |
| 
 | |
| function convertLocalDatetimeToUTC(value: string) {
 | |
|   const date = new Date(value);
 | |
|   const fullISO8601 = date.toISOString();
 | |
|   return fullISO8601.slice(0, fullISO8601.indexOf('T') + 6);
 | |
| }
 | |
| 
 | |
| Rails.delegate(
 | |
|   document,
 | |
|   '#form_admin_settings_registrations_mode',
 | |
|   'change',
 | |
|   ({ target }) => {
 | |
|     if (target instanceof HTMLSelectElement) onChangeRegistrationMode(target);
 | |
|   },
 | |
| );
 | |
| 
 | |
| async function mountReactComponent(element: Element) {
 | |
|   const componentName = element.getAttribute('data-admin-component');
 | |
|   const stringProps = element.getAttribute('data-props');
 | |
| 
 | |
|   if (!stringProps) return;
 | |
| 
 | |
|   const componentProps = JSON.parse(stringProps) as object;
 | |
| 
 | |
|   const { default: AdminComponent } = await import(
 | |
|     '@/mastodon/containers/admin_component'
 | |
|   );
 | |
| 
 | |
|   const { default: Component } = (await import(
 | |
|     `@/mastodon/components/admin/${componentName}`
 | |
|   )) as { default: React.ComponentType };
 | |
| 
 | |
|   const root = createRoot(element);
 | |
| 
 | |
|   root.render(
 | |
|     <AdminComponent>
 | |
|       <Component {...componentProps} />
 | |
|     </AdminComponent>,
 | |
|   );
 | |
| }
 | |
| 
 | |
| ready(() => {
 | |
|   const domainBlockSeveritySelect = document.querySelector<HTMLSelectElement>(
 | |
|     'select#domain_block_severity',
 | |
|   );
 | |
|   if (domainBlockSeveritySelect)
 | |
|     onDomainBlockSeverityChange(domainBlockSeveritySelect);
 | |
| 
 | |
|   const enableBootstrapTimelineAccounts =
 | |
|     document.querySelector<HTMLInputElement>(
 | |
|       'input#form_admin_settings_enable_bootstrap_timeline_accounts',
 | |
|     );
 | |
|   if (enableBootstrapTimelineAccounts)
 | |
|     onEnableBootstrapTimelineAccountsChange(enableBootstrapTimelineAccounts);
 | |
| 
 | |
|   const registrationMode = document.querySelector<HTMLSelectElement>(
 | |
|     'select#form_admin_settings_registrations_mode',
 | |
|   );
 | |
|   if (registrationMode) onChangeRegistrationMode(registrationMode);
 | |
| 
 | |
|   const checkAllElement = document.querySelector<HTMLInputElement>(
 | |
|     'input#batch_checkbox_all',
 | |
|   );
 | |
|   if (checkAllElement) {
 | |
|     const allCheckboxes = Array.from(
 | |
|       document.querySelectorAll<HTMLInputElement>(batchCheckboxClassName),
 | |
|     );
 | |
|     checkAllElement.checked = allCheckboxes.every((content) => content.checked);
 | |
|     checkAllElement.indeterminate =
 | |
|       !checkAllElement.checked &&
 | |
|       allCheckboxes.some((content) => content.checked);
 | |
|   }
 | |
| 
 | |
|   document
 | |
|     .querySelector('a#add-instance-button')
 | |
|     ?.addEventListener('click', (e) => {
 | |
|       const domain = document.querySelector<HTMLInputElement>(
 | |
|         'input[type="text"]#by_domain',
 | |
|       )?.value;
 | |
| 
 | |
|       if (domain && e.target instanceof HTMLAnchorElement) {
 | |
|         const url = new URL(e.target.href);
 | |
|         url.searchParams.set('_domain', domain);
 | |
|         e.target.href = url.toString();
 | |
|       }
 | |
|     });
 | |
| 
 | |
|   document
 | |
|     .querySelectorAll<HTMLInputElement>('input[type="datetime-local"]')
 | |
|     .forEach((element) => {
 | |
|       if (element.value) {
 | |
|         element.value = convertUTCDateTimeToLocal(element.value);
 | |
|       }
 | |
|       if (element.placeholder) {
 | |
|         element.placeholder = convertUTCDateTimeToLocal(element.placeholder);
 | |
|       }
 | |
|     });
 | |
| 
 | |
|   Rails.delegate(document, 'form', 'submit', ({ target }) => {
 | |
|     if (target instanceof HTMLFormElement)
 | |
|       target
 | |
|         .querySelectorAll<HTMLInputElement>('input[type="datetime-local"]')
 | |
|         .forEach((element) => {
 | |
|           if (element.value && element.validity.valid) {
 | |
|             element.value = convertLocalDatetimeToUTC(element.value);
 | |
|           }
 | |
|         });
 | |
|   });
 | |
| 
 | |
|   const announcementStartsAt = document.querySelector<HTMLInputElement>(
 | |
|     'input[type="datetime-local"]#announcement_starts_at',
 | |
|   );
 | |
|   if (announcementStartsAt) {
 | |
|     setAnnouncementEndsAttributes(announcementStartsAt);
 | |
|   }
 | |
| 
 | |
|   document.querySelectorAll('[data-admin-component]').forEach((element) => {
 | |
|     void mountReactComponent(element);
 | |
|   });
 | |
| }).catch((reason: unknown) => {
 | |
|   throw reason;
 | |
| });
 |