gitea源码

common-page.ts 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import {GET} from '../modules/fetch.ts';
  2. import {showGlobalErrorMessage} from '../bootstrap.ts';
  3. import {fomanticQuery} from '../modules/fomantic/base.ts';
  4. import {queryElems} from '../utils/dom.ts';
  5. import {registerGlobalInitFunc, registerGlobalSelectorFunc} from '../modules/observer.ts';
  6. import {initAvatarUploaderWithCropper} from './comp/Cropper.ts';
  7. import {initCompSearchRepoBox} from './comp/SearchRepoBox.ts';
  8. const {appUrl} = window.config;
  9. export function initHeadNavbarContentToggle() {
  10. const navbar = document.querySelector('#navbar');
  11. const btn = document.querySelector('#navbar-expand-toggle');
  12. if (!navbar || !btn) return;
  13. btn.addEventListener('click', () => {
  14. const isExpanded = btn.classList.contains('active');
  15. navbar.classList.toggle('navbar-menu-open', !isExpanded);
  16. btn.classList.toggle('active', !isExpanded);
  17. });
  18. }
  19. export function initFootLanguageMenu() {
  20. document.querySelector('.ui.dropdown .menu.language-menu')?.addEventListener('click', async (e) => {
  21. const item = (e.target as HTMLElement).closest('.item');
  22. if (!item) return;
  23. e.preventDefault();
  24. await GET(item.getAttribute('data-url'));
  25. window.location.reload();
  26. });
  27. }
  28. export function initGlobalDropdown() {
  29. // do not init "custom" dropdowns, "custom" dropdowns are managed by their own code.
  30. registerGlobalSelectorFunc('.ui.dropdown:not(.custom)', (el) => {
  31. const $dropdown = fomanticQuery(el);
  32. if ($dropdown.data('module-dropdown')) return; // do not re-init if other code has already initialized it.
  33. $dropdown.dropdown('setting', {hideDividers: 'empty'});
  34. if (el.classList.contains('jump')) {
  35. // The "jump" means this dropdown is mainly used for "menu" purpose,
  36. // clicking an item will jump to somewhere else or trigger an action/function.
  37. // When a dropdown is used for non-refresh actions with tippy,
  38. // it must have this "jump" class to hide the tippy when dropdown is closed.
  39. $dropdown.dropdown('setting', {
  40. action: 'hide',
  41. onShow() {
  42. // hide associated tooltip while dropdown is open
  43. this._tippy?.hide();
  44. this._tippy?.disable();
  45. },
  46. onHide() {
  47. this._tippy?.enable();
  48. // eslint-disable-next-line unicorn/no-this-assignment
  49. const elDropdown = this;
  50. // hide all tippy elements of items after a while. eg: use Enter to click "Copy Link" in the Issue Context Menu
  51. setTimeout(() => {
  52. const $dropdown = fomanticQuery(elDropdown);
  53. if ($dropdown.dropdown('is hidden')) {
  54. queryElems(elDropdown, '.menu > .item', (el) => el._tippy?.hide());
  55. }
  56. }, 2000);
  57. },
  58. });
  59. }
  60. // Special popup-directions, prevent Fomantic from guessing the popup direction.
  61. // With default "direction: auto", if the viewport height is small, Fomantic would show the popup upward,
  62. // if the dropdown is at the beginning of the page, then the top part would be clipped by the window view.
  63. // eg: Issue List "Sort" dropdown
  64. // But we can not set "direction: downward" for all dropdowns, because there is a bug in dropdown menu positioning when calculating the "left" position,
  65. // which would make some dropdown popups slightly shift out of the right viewport edge in some cases.
  66. // eg: the "Create New Repo" menu on the navbar.
  67. if (el.classList.contains('upward')) $dropdown.dropdown('setting', 'direction', 'upward');
  68. if (el.classList.contains('downward')) $dropdown.dropdown('setting', 'direction', 'downward');
  69. });
  70. }
  71. export function initGlobalComponent() {
  72. fomanticQuery('.ui.menu.tabular:not(.custom) .item').tab();
  73. registerGlobalInitFunc('initAvatarUploader', initAvatarUploaderWithCropper);
  74. registerGlobalInitFunc('initSearchRepoBox', initCompSearchRepoBox);
  75. }
  76. // for performance considerations, it only uses performant syntax
  77. function attachInputDirAuto(el: Partial<HTMLInputElement | HTMLTextAreaElement>) {
  78. if (el.type !== 'hidden' &&
  79. el.type !== 'checkbox' &&
  80. el.type !== 'radio' &&
  81. el.type !== 'range' &&
  82. el.type !== 'color') {
  83. el.dir = 'auto';
  84. }
  85. }
  86. export function initGlobalInput() {
  87. registerGlobalSelectorFunc('input, textarea', attachInputDirAuto);
  88. registerGlobalInitFunc('initInputAutoFocusEnd', (el: HTMLInputElement) => {
  89. el.focus(); // expects only one such element on one page. If there are many, then the last one gets the focus.
  90. el.setSelectionRange(el.value.length, el.value.length);
  91. });
  92. }
  93. /**
  94. * Too many users set their ROOT_URL to wrong value, and it causes a lot of problems:
  95. * * Cross-origin API request without correct cookie
  96. * * Incorrect href in <a>
  97. * * ...
  98. * So we check whether current URL starts with AppUrl(ROOT_URL).
  99. * If they don't match, show a warning to users.
  100. */
  101. export function checkAppUrl() {
  102. const curUrl = window.location.href;
  103. // some users visit "https://domain/gitea" while appUrl is "https://domain/gitea/", there should be no warning
  104. if (curUrl.startsWith(appUrl) || `${curUrl}/` === appUrl) {
  105. return;
  106. }
  107. showGlobalErrorMessage(`Your ROOT_URL in app.ini is "${appUrl}", it's unlikely matching the site you are visiting.
  108. Mismatched ROOT_URL config causes wrong URL links for web UI/mail content/webhook notification/OAuth2 sign-in.`, 'warning');
  109. }
  110. export function checkAppUrlScheme() {
  111. const curUrl = window.location.href;
  112. // some users visit "http://domain" while appUrl is "https://domain", COOKIE_SECURE makes it impossible to sign in
  113. if (curUrl.startsWith('http:') && appUrl.startsWith('https:')) {
  114. showGlobalErrorMessage(`This instance is configured to run under HTTPS (by ROOT_URL config), you are accessing by HTTP. Mismatched scheme might cause problems for sign-in/sign-up.`, 'warning');
  115. }
  116. }