forked from go-gitea/gitea
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcommon-page.ts
More file actions
170 lines (151 loc) · 7.78 KB
/
common-page.ts
File metadata and controls
170 lines (151 loc) · 7.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import {GET, POST} from '../modules/fetch.ts';
import {showGlobalErrorMessage} from '../modules/errors.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {initTabs} from '../modules/fomantic/tab.ts';
import {addDelegatedEventListener, queryElems} from '../utils/dom.ts';
import {registerGlobalInitFunc, registerGlobalSelectorFunc} from '../modules/observer.ts';
import {initAvatarUploaderWithCropper} from './comp/Cropper.ts';
import {initCompSearchRepoBox} from './comp/SearchRepoBox.ts';
const {appUrl, appSubUrl} = window.config;
function initHeadNavbarContentToggle() {
const navbar = document.querySelector('#navbar');
const btn = document.querySelector('#navbar-expand-toggle');
if (!navbar || !btn) return;
btn.addEventListener('click', () => {
const isExpanded = btn.classList.contains('active');
navbar.classList.toggle('navbar-menu-open', !isExpanded);
btn.classList.toggle('active', !isExpanded);
});
}
function initFooterLanguageMenu() {
document.querySelector('.ui.dropdown .menu.language-menu')?.addEventListener('click', async (e) => {
const item = (e.target as HTMLElement).closest('.item');
if (!item) return;
e.preventDefault();
await GET(item.getAttribute('data-url')!);
window.location.reload();
});
}
function initFooterThemeSelector() {
const elDropdown = document.querySelector('#footer-theme-selector');
if (!elDropdown) return; // some pages don't have footer, for example: 500.tmpl
const $dropdown = fomanticQuery(elDropdown);
$dropdown.dropdown({
direction: 'upward',
apiSettings: {url: `${appSubUrl}/-/web-theme/list`, cache: false},
});
addDelegatedEventListener(elDropdown, 'click', '.menu > .item', async (el) => {
const themeName = el.getAttribute('data-value')!;
await POST(`${appSubUrl}/-/web-theme/apply?theme=${encodeURIComponent(themeName)}`);
window.location.reload();
});
}
export function initCommmPageComponents() {
initHeadNavbarContentToggle();
initFooterLanguageMenu();
initFooterThemeSelector();
}
export function initGlobalDropdown() {
// do not init "custom" dropdowns, "custom" dropdowns are managed by their own code.
registerGlobalSelectorFunc('.ui.dropdown:not(.custom)', (el) => {
const $dropdown = fomanticQuery(el);
if ($dropdown.data('module-dropdown')) return; // do not re-init if other code has already initialized it.
$dropdown.dropdown('setting', {hideDividers: 'empty'});
if (el.classList.contains('jump')) {
// The "jump" means this dropdown is mainly used for "menu" purpose,
// clicking an item will jump to somewhere else or trigger an action/function.
// When a dropdown is used for non-refresh actions with tippy,
// it must have this "jump" class to hide the tippy when dropdown is closed.
$dropdown.dropdown('setting', {
action: 'hide',
onShow() {
// hide associated tooltip while dropdown is open
this._tippy?.hide();
this._tippy?.disable();
},
onHide() {
this._tippy?.enable();
// eslint-disable-next-line unicorn/no-this-assignment
const elDropdown = this;
// hide all tippy elements of items after a while. eg: use Enter to click "Copy Link" in the Issue Context Menu
setTimeout(() => {
const $dropdown = fomanticQuery(elDropdown);
if ($dropdown.dropdown('is hidden')) {
queryElems(elDropdown, '.menu > .item', (el) => el._tippy?.hide());
}
}, 2000);
},
});
}
// Special popup-directions, prevent Fomantic from guessing the popup direction.
// With default "direction: auto", if the viewport height is small, Fomantic would show the popup upward,
// if the dropdown is at the beginning of the page, then the top part would be clipped by the window view.
// eg: Issue List "Sort" dropdown
// But we can not set "direction: downward" for all dropdowns, because there is a bug in dropdown menu positioning when calculating the "left" position,
// which would make some dropdown popups slightly shift out of the right viewport edge in some cases.
// eg: the "Create New Repo" menu on the navbar.
if (el.classList.contains('upward')) $dropdown.dropdown('setting', 'direction', 'upward');
if (el.classList.contains('downward')) $dropdown.dropdown('setting', 'direction', 'downward');
});
}
export function initGlobalComponent() {
initTabs(document.querySelectorAll('.ui.menu.tabular:not(.custom) .item'));
registerGlobalInitFunc('initAvatarUploader', initAvatarUploaderWithCropper);
registerGlobalInitFunc('initSearchRepoBox', initCompSearchRepoBox);
}
// for performance considerations, it only uses performant syntax
function attachInputDirAuto(el: Partial<HTMLInputElement | HTMLTextAreaElement>) {
if (el.type !== 'hidden' &&
el.type !== 'checkbox' &&
el.type !== 'radio' &&
el.type !== 'range' &&
el.type !== 'color') {
el.dir = 'auto';
}
}
function autoFocusEnd(el: HTMLInputElement | HTMLTextAreaElement) {
el.focus();
el.setSelectionRange(el.value.length, el.value.length);
}
export function applyAutoFocus(container: Element) {
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/autofocus
// "autofocus" behavior is defined by the standard: when a container (e.g.: dialog) becomes visible, focus the element with "autofocus" attribute
// Fomantic UI already supports it for its modal dialog, we need to cover more cases (e.g.: ".show-panel" button)
// Here is just a simple support, we don't expect more than one element that need "autofocus" appearing in the same container
container.querySelector<HTMLElement>('[autofocus]')?.focus();
// Also, apply our autoFocusEnd behavior
// TODO: GLOBAL-INIT-MULTIPLE-FUNCTIONS: use "~=" operator in case we would extend the "data-global-init" to support more functions in the future.
const el = container.querySelector<HTMLInputElement>('[data-global-init~="autoFocusEnd"]');
if (el) autoFocusEnd(el);
}
export function initGlobalInput() {
registerGlobalSelectorFunc('input, textarea', attachInputDirAuto);
// autoFocusEnd is used for autofocus an input/textarea and move the cursor to the end of the text.
// It is useful for "New Issue"/"New PR" pages when the title is pre-filled with prefix text (e.g.: from template or commit message)
// The native "autofocus" isn't used because there is a delay between "focused (DOM rendering)" and "move cursor to end (our JS)", it causes flickers.
registerGlobalInitFunc('autoFocusEnd', autoFocusEnd);
}
/**
* Too many users set their ROOT_URL to wrong value, and it causes a lot of problems:
* * Cross-origin API request without correct cookie
* * Incorrect href in <a>
* * ...
* So we check whether current URL starts with AppUrl(ROOT_URL).
* If they don't match, show a warning to users.
*/
export function checkAppUrl() {
const curUrl = window.location.href;
// some users visit "https://domain/gitea" while appUrl is "https://domain/gitea/", there should be no warning
if (curUrl.startsWith(appUrl) || `${curUrl}/` === appUrl) {
return;
}
showGlobalErrorMessage(`The detected web site URL is "${appUrl}", it's unlikely matching the site config.
Mismatched app.ini ROOT_URL or reverse proxy "Host/X-Forwarded-Proto" config might cause wrong URL links for web UI/mail content/webhook notification/OAuth2 sign-in.`, 'warning');
}
export function checkAppUrlScheme() {
const curUrl = window.location.href;
// some users visit "http://domain" while appUrl is "https://domain", COOKIE_SECURE makes it impossible to sign in
if (curUrl.startsWith('http:') && appUrl.startsWith('https:')) {
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');
}
}