2022-01-28 16:00:11 -05:00
|
|
|
import $ from 'jquery';
|
|
|
|
import 'jquery.are-you-sure';
|
2021-10-16 13:28:04 -04:00
|
|
|
import {mqBinarySearch} from '../utils.js';
|
|
|
|
import createDropzone from './dropzone.js';
|
|
|
|
import {initCompColorPicker} from './comp/ColorPicker.js';
|
Show messages for users if the ROOT_URL is wrong, show JavaScript errors (#18971)
* ROOT_URL issues: some users did wrong to there app.ini config, then:
* The assets can not be loaded (AppSubUrl != "" and users try to access http://host:3000/)
*The ROOT_URL is wrong, then many URLs in Gitea are broken.
Now Gitea show enough information to users.
* JavaScript error issues, there are many users affected by JavaScript errors, some are caused by frontend bugs, some are caused by broken customized templates. If these JS errors can be found at first time, then maintainers do not need to ask about how bug occurs again and again.
* Some people like to modify the `head.tmpl`, so we separate the script part to `head_script.tmpl`, then it's much safer.
* use specialized CSS class "js-global-error", end users still have a chance to hide error messages by customized CSS styles.
2022-03-30 01:52:24 -04:00
|
|
|
import {showGlobalErrorMessage} from '../bootstrap.js';
|
2021-10-16 13:28:04 -04:00
|
|
|
|
Show messages for users if the ROOT_URL is wrong, show JavaScript errors (#18971)
* ROOT_URL issues: some users did wrong to there app.ini config, then:
* The assets can not be loaded (AppSubUrl != "" and users try to access http://host:3000/)
*The ROOT_URL is wrong, then many URLs in Gitea are broken.
Now Gitea show enough information to users.
* JavaScript error issues, there are many users affected by JavaScript errors, some are caused by frontend bugs, some are caused by broken customized templates. If these JS errors can be found at first time, then maintainers do not need to ask about how bug occurs again and again.
* Some people like to modify the `head.tmpl`, so we separate the script part to `head_script.tmpl`, then it's much safer.
* use specialized CSS class "js-global-error", end users still have a chance to hide error messages by customized CSS styles.
2022-03-30 01:52:24 -04:00
|
|
|
const {appUrl, csrfToken} = window.config;
|
2021-10-16 13:28:04 -04:00
|
|
|
|
|
|
|
export function initGlobalFormDirtyLeaveConfirm() {
|
|
|
|
// Warn users that try to leave a page after entering data into a form.
|
|
|
|
// Except on sign-in pages, and for forms marked as 'ignore-dirty'.
|
|
|
|
if ($('.user.signin').length === 0) {
|
|
|
|
$('form:not(.ignore-dirty)').areYouSure();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function initHeadNavbarContentToggle() {
|
|
|
|
const content = $('#navbar');
|
|
|
|
const toggle = $('#navbar-expand-toggle');
|
|
|
|
let isExpanded = false;
|
|
|
|
toggle.on('click', () => {
|
|
|
|
isExpanded = !isExpanded;
|
|
|
|
if (isExpanded) {
|
|
|
|
content.addClass('shown');
|
|
|
|
toggle.addClass('active');
|
|
|
|
} else {
|
|
|
|
content.removeClass('shown');
|
|
|
|
toggle.removeClass('active');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export function initFootLanguageMenu() {
|
|
|
|
function linkLanguageAction() {
|
|
|
|
const $this = $(this);
|
|
|
|
$.post($this.data('url')).always(() => {
|
|
|
|
window.location.reload();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
$('.language-menu a[lang]').on('click', linkLanguageAction);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function initGlobalEnterQuickSubmit() {
|
2022-05-19 22:26:04 -04:00
|
|
|
$(document).on('keydown', '.js-quick-submit', (e) => {
|
|
|
|
if (((e.ctrlKey && !e.altKey) || e.metaKey) && (e.key === 'Enter')) {
|
|
|
|
handleGlobalEnterQuickSubmit(e.target);
|
|
|
|
return false;
|
2021-10-16 13:28:04 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-05-19 22:26:04 -04:00
|
|
|
export function handleGlobalEnterQuickSubmit(target) {
|
|
|
|
const $target = $(target);
|
|
|
|
const $form = $(target).closest('form');
|
|
|
|
if ($form.length) {
|
|
|
|
// here use the event to trigger the submit event (instead of calling `submit()` method directly)
|
|
|
|
// otherwise the `areYouSure` handler won't be executed, then there will be an annoying "confirm to leave" dialog
|
|
|
|
$form.trigger('submit');
|
|
|
|
} else {
|
|
|
|
// if no form, then the editor is for an AJAX request, dispatch an event to the target, let the target's event handler to do the AJAX request.
|
|
|
|
// the 'ce-' prefix means this is a CustomEvent
|
|
|
|
$target.trigger('ce-quick-submit');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-16 13:28:04 -04:00
|
|
|
export function initGlobalButtonClickOnEnter() {
|
|
|
|
$(document).on('keypress', '.ui.button', (e) => {
|
|
|
|
if (e.keyCode === 13 || e.keyCode === 32) { // enter key or space bar
|
|
|
|
$(e.target).trigger('click');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export function initGlobalCommon() {
|
|
|
|
// Show exact time
|
|
|
|
$('.time-since').each(function () {
|
|
|
|
$(this)
|
2021-11-17 22:26:50 -05:00
|
|
|
.addClass('tooltip')
|
2021-10-16 13:28:04 -04:00
|
|
|
.attr('data-content', $(this).attr('title'))
|
|
|
|
.attr('title', '');
|
|
|
|
});
|
|
|
|
|
|
|
|
// Undo Safari emoji glitch fix at high enough zoom levels
|
|
|
|
if (navigator.userAgent.match('Safari')) {
|
|
|
|
$(window).resize(() => {
|
|
|
|
const px = mqBinarySearch('width', 0, 4096, 1, 'px');
|
|
|
|
const em = mqBinarySearch('width', 0, 1024, 0.01, 'em');
|
|
|
|
if (em * 16 * 1.25 - px <= -1) {
|
|
|
|
$('body').addClass('safari-above125');
|
|
|
|
} else {
|
|
|
|
$('body').removeClass('safari-above125');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Semantic UI modules.
|
|
|
|
$('.dropdown:not(.custom)').dropdown({
|
|
|
|
fullTextSearch: 'exact'
|
|
|
|
});
|
|
|
|
$('.jump.dropdown').dropdown({
|
|
|
|
action: 'hide',
|
|
|
|
onShow() {
|
2021-11-17 22:26:50 -05:00
|
|
|
$('.tooltip').popup('hide');
|
2021-10-16 13:28:04 -04:00
|
|
|
},
|
|
|
|
fullTextSearch: 'exact'
|
|
|
|
});
|
|
|
|
$('.slide.up.dropdown').dropdown({
|
|
|
|
transition: 'slide up',
|
|
|
|
fullTextSearch: 'exact'
|
|
|
|
});
|
|
|
|
$('.upward.dropdown').dropdown({
|
|
|
|
direction: 'upward',
|
|
|
|
fullTextSearch: 'exact'
|
|
|
|
});
|
|
|
|
$('.ui.checkbox').checkbox();
|
2021-11-17 22:26:50 -05:00
|
|
|
|
|
|
|
// init popups
|
|
|
|
$('.tooltip').each((_, el) => {
|
|
|
|
const $el = $(el);
|
|
|
|
const attr = $el.attr('data-variation');
|
|
|
|
const attrs = attr ? attr.split(' ') : [];
|
|
|
|
const variations = new Set([...attrs, 'inverted', 'tiny']);
|
|
|
|
$el.attr('data-variation', [...variations].join(' ')).popup();
|
|
|
|
});
|
|
|
|
|
|
|
|
$('.top.menu .tooltip').popup({
|
2021-10-16 13:28:04 -04:00
|
|
|
onShow() {
|
|
|
|
if ($('.top.menu .menu.transition').hasClass('visible')) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
$('.tabular.menu .item').tab();
|
|
|
|
$('.tabable.menu .item').tab();
|
|
|
|
|
|
|
|
$('.toggle.button').on('click', function () {
|
2022-01-16 06:19:26 -05:00
|
|
|
$($(this).data('target')).slideToggle(100);
|
2021-10-16 13:28:04 -04:00
|
|
|
});
|
|
|
|
|
2021-10-21 14:19:32 -04:00
|
|
|
// make table <tr> and <td> elements clickable like a link
|
|
|
|
$('tr[data-href], td[data-href]').on('click', function (e) {
|
|
|
|
const href = $(this).data('href');
|
|
|
|
if (e.target.nodeName === 'A') {
|
|
|
|
// if a user clicks on <a>, then the <tr> or <td> should not act as a link.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (e.ctrlKey || e.metaKey) {
|
|
|
|
// ctrl+click or meta+click opens a new window in modern browsers
|
|
|
|
window.open(href);
|
|
|
|
} else {
|
|
|
|
window.location = href;
|
|
|
|
}
|
2021-10-16 13:28:04 -04:00
|
|
|
});
|
2022-05-07 08:24:02 -04:00
|
|
|
|
|
|
|
// loading-button this logic used to prevent push one form more than one time
|
|
|
|
$(document).on('click', '.button.loading-button', function (e) {
|
|
|
|
const $btn = $(this);
|
|
|
|
|
|
|
|
if ($btn.hasClass('loading')) {
|
|
|
|
e.preventDefault();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$btn.addClass('loading disabled');
|
|
|
|
});
|
2021-10-16 13:28:04 -04:00
|
|
|
}
|
|
|
|
|
2021-11-09 04:27:25 -05:00
|
|
|
export function initGlobalDropzone() {
|
2021-10-16 13:28:04 -04:00
|
|
|
// Dropzone
|
|
|
|
for (const el of document.querySelectorAll('.dropzone')) {
|
|
|
|
const $dropzone = $(el);
|
2021-11-09 04:27:25 -05:00
|
|
|
const _promise = createDropzone(el, {
|
2021-10-16 13:28:04 -04:00
|
|
|
url: $dropzone.data('upload-url'),
|
2021-10-21 03:37:43 -04:00
|
|
|
headers: {'X-Csrf-Token': csrfToken},
|
2021-10-16 13:28:04 -04:00
|
|
|
maxFiles: $dropzone.data('max-file'),
|
|
|
|
maxFilesize: $dropzone.data('max-size'),
|
|
|
|
acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'),
|
|
|
|
addRemoveLinks: true,
|
|
|
|
dictDefaultMessage: $dropzone.data('default-message'),
|
|
|
|
dictInvalidFileType: $dropzone.data('invalid-input-type'),
|
|
|
|
dictFileTooBig: $dropzone.data('file-too-big'),
|
|
|
|
dictRemoveFile: $dropzone.data('remove-file'),
|
|
|
|
timeout: 0,
|
|
|
|
thumbnailMethod: 'contain',
|
|
|
|
thumbnailWidth: 480,
|
|
|
|
thumbnailHeight: 480,
|
|
|
|
init() {
|
|
|
|
this.on('success', (_file, data) => {
|
|
|
|
const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
|
|
|
|
$dropzone.find('.files').append(input);
|
|
|
|
});
|
|
|
|
this.on('removedfile', (file) => {
|
|
|
|
$(`#${file.uuid}`).remove();
|
|
|
|
if ($dropzone.data('remove-url')) {
|
|
|
|
$.post($dropzone.data('remove-url'), {
|
|
|
|
file: file.uuid,
|
2021-10-21 03:37:43 -04:00
|
|
|
_csrf: csrfToken,
|
2021-10-16 13:28:04 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function initGlobalLinkActions() {
|
|
|
|
function showDeletePopup() {
|
|
|
|
const $this = $(this);
|
|
|
|
const dataArray = $this.data();
|
|
|
|
let filter = '';
|
|
|
|
if ($this.data('modal-id')) {
|
|
|
|
filter += `#${$this.data('modal-id')}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dialog = $(`.delete.modal${filter}`);
|
|
|
|
dialog.find('.name').text($this.data('name'));
|
|
|
|
for (const [key, value] of Object.entries(dataArray)) {
|
|
|
|
if (key && key.startsWith('data')) {
|
|
|
|
dialog.find(`.${key}`).text(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dialog.modal({
|
|
|
|
closable: false,
|
|
|
|
onApprove() {
|
|
|
|
if ($this.data('type') === 'form') {
|
2022-01-16 06:19:26 -05:00
|
|
|
$($this.data('form')).trigger('submit');
|
2021-10-16 13:28:04 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const postData = {
|
2021-10-21 03:37:43 -04:00
|
|
|
_csrf: csrfToken,
|
2021-10-16 13:28:04 -04:00
|
|
|
};
|
|
|
|
for (const [key, value] of Object.entries(dataArray)) {
|
|
|
|
if (key && key.startsWith('data')) {
|
2022-02-18 01:50:36 -05:00
|
|
|
postData[key.slice(4)] = value;
|
2021-10-16 13:28:04 -04:00
|
|
|
}
|
|
|
|
if (key === 'id') {
|
|
|
|
postData['id'] = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$.post($this.data('url'), postData).done((data) => {
|
|
|
|
window.location.href = data.redirect;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}).modal('show');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function showAddAllPopup() {
|
|
|
|
const $this = $(this);
|
|
|
|
let filter = '';
|
|
|
|
if ($this.attr('id')) {
|
|
|
|
filter += `#${$this.attr('id')}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dialog = $(`.addall.modal${filter}`);
|
|
|
|
dialog.find('.name').text($this.data('name'));
|
|
|
|
|
|
|
|
dialog.modal({
|
|
|
|
closable: false,
|
|
|
|
onApprove() {
|
|
|
|
if ($this.data('type') === 'form') {
|
2022-01-16 06:19:26 -05:00
|
|
|
$($this.data('form')).trigger('submit');
|
2021-10-16 13:28:04 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$.post($this.data('url'), {
|
2021-10-21 03:37:43 -04:00
|
|
|
_csrf: csrfToken,
|
2021-10-16 13:28:04 -04:00
|
|
|
id: $this.data('id')
|
|
|
|
}).done((data) => {
|
|
|
|
window.location.href = data.redirect;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}).modal('show');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function linkAction(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
const $this = $(this);
|
|
|
|
const redirect = $this.data('redirect');
|
|
|
|
$.post($this.data('url'), {
|
2021-10-21 03:37:43 -04:00
|
|
|
_csrf: csrfToken
|
2021-10-16 13:28:04 -04:00
|
|
|
}).done((data) => {
|
|
|
|
if (data.redirect) {
|
|
|
|
window.location.href = data.redirect;
|
|
|
|
} else if (redirect) {
|
|
|
|
window.location.href = redirect;
|
|
|
|
} else {
|
|
|
|
window.location.reload();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helpers.
|
|
|
|
$('.delete-button').on('click', showDeletePopup);
|
|
|
|
$('.link-action').on('click', linkAction);
|
|
|
|
|
|
|
|
// FIXME: this function is only used once, and not common, not well designed. should be refactored later
|
|
|
|
$('.add-all-button').on('click', showAddAllPopup);
|
|
|
|
|
|
|
|
// FIXME: this is only used once, and should be replace with `link-action` instead
|
|
|
|
$('.undo-button').on('click', function () {
|
|
|
|
const $this = $(this);
|
|
|
|
$.post($this.data('url'), {
|
2021-10-21 03:37:43 -04:00
|
|
|
_csrf: csrfToken,
|
2021-10-16 13:28:04 -04:00
|
|
|
id: $this.data('id')
|
|
|
|
}).done((data) => {
|
|
|
|
window.location.href = data.redirect;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export function initGlobalButtons() {
|
|
|
|
$('.show-panel.button').on('click', function () {
|
2022-01-16 06:19:26 -05:00
|
|
|
$($(this).data('panel')).show();
|
2021-10-16 13:28:04 -04:00
|
|
|
});
|
|
|
|
|
2021-10-28 06:55:48 -04:00
|
|
|
$('.hide-panel.button').on('click', function (event) {
|
2022-01-06 20:18:52 -05:00
|
|
|
// a `.hide-panel.button` can hide a panel, by `data-panel="selector"` or `data-panel-closest="selector"`
|
2021-10-28 06:55:48 -04:00
|
|
|
event.preventDefault();
|
2022-01-06 20:18:52 -05:00
|
|
|
let sel = $(this).attr('data-panel');
|
|
|
|
if (sel) {
|
2022-01-16 06:19:26 -05:00
|
|
|
$(sel).hide();
|
2022-01-06 20:18:52 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
sel = $(this).attr('data-panel-closest');
|
|
|
|
if (sel) {
|
|
|
|
$(this).closest(sel).hide();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// should never happen, otherwise there is a bug in code
|
|
|
|
alert('Nothing to hide');
|
2021-10-16 13:28:04 -04:00
|
|
|
});
|
|
|
|
|
2022-02-09 15:28:55 -05:00
|
|
|
$('.show-modal').on('click', function () {
|
|
|
|
const modalDiv = $($(this).attr('data-modal'));
|
|
|
|
for (const attrib of this.attributes) {
|
|
|
|
if (!attrib.name.startsWith('data-modal-')) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const id = attrib.name.substring(11);
|
|
|
|
const target = modalDiv.find(`#${id}`);
|
|
|
|
if (target.is('input')) {
|
|
|
|
target.val(attrib.value);
|
|
|
|
} else {
|
|
|
|
target.text(attrib.value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
modalDiv.modal('show');
|
|
|
|
const colorPickers = $($(this).attr('data-modal')).find('.color-picker');
|
2021-10-16 13:28:04 -04:00
|
|
|
if (colorPickers.length > 0) {
|
|
|
|
initCompColorPicker();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
$('.delete-post.button').on('click', function () {
|
|
|
|
const $this = $(this);
|
2022-02-09 15:28:55 -05:00
|
|
|
$.post($this.attr('data-request-url'), {
|
2021-10-21 03:37:43 -04:00
|
|
|
_csrf: csrfToken
|
2021-10-16 13:28:04 -04:00
|
|
|
}).done(() => {
|
2022-02-09 15:28:55 -05:00
|
|
|
window.location.href = $this.attr('data-done-url');
|
2021-10-16 13:28:04 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
Show messages for users if the ROOT_URL is wrong, show JavaScript errors (#18971)
* ROOT_URL issues: some users did wrong to there app.ini config, then:
* The assets can not be loaded (AppSubUrl != "" and users try to access http://host:3000/)
*The ROOT_URL is wrong, then many URLs in Gitea are broken.
Now Gitea show enough information to users.
* JavaScript error issues, there are many users affected by JavaScript errors, some are caused by frontend bugs, some are caused by broken customized templates. If these JS errors can be found at first time, then maintainers do not need to ask about how bug occurs again and again.
* Some people like to modify the `head.tmpl`, so we separate the script part to `head_script.tmpl`, then it's much safer.
* use specialized CSS class "js-global-error", end users still have a chance to hide error messages by customized CSS styles.
2022-03-30 01:52:24 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
if (curUrl.startsWith(appUrl)) {
|
|
|
|
return;
|
|
|
|
}
|
2022-04-01 04:00:26 -04:00
|
|
|
if (document.querySelector('.page-content.install')) {
|
|
|
|
return; // no need to show the message on the installation page
|
|
|
|
}
|
Show messages for users if the ROOT_URL is wrong, show JavaScript errors (#18971)
* ROOT_URL issues: some users did wrong to there app.ini config, then:
* The assets can not be loaded (AppSubUrl != "" and users try to access http://host:3000/)
*The ROOT_URL is wrong, then many URLs in Gitea are broken.
Now Gitea show enough information to users.
* JavaScript error issues, there are many users affected by JavaScript errors, some are caused by frontend bugs, some are caused by broken customized templates. If these JS errors can be found at first time, then maintainers do not need to ask about how bug occurs again and again.
* Some people like to modify the `head.tmpl`, so we separate the script part to `head_script.tmpl`, then it's much safer.
* use specialized CSS class "js-global-error", end users still have a chance to hide error messages by customized CSS styles.
2022-03-30 01:52:24 -04:00
|
|
|
showGlobalErrorMessage(`Your ROOT_URL in app.ini is ${appUrl} but you are visiting ${curUrl}
|
|
|
|
You should set ROOT_URL correctly, otherwise the web may not work correctly.`);
|
|
|
|
}
|