mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-21 01:21:18 +02:00
Rework delete org and rename org UI (#34762)
# What's the problem of the original implementation Renaming organization will mix with organization's information change make the operation difficult to keep consistent. This PR created a danger zone like what's repository setting. It also moved organization's `rename` and `delete` operations to this zone. The original updating repository will not change the name any more. This is also a step to extract the `updaterepository` function completely. Before:   After:     --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -30,6 +30,10 @@
|
||||
--page-spacing: 16px; /* space between page elements */
|
||||
--page-margin-x: 32px; /* minimum space on left and right side of page */
|
||||
--page-space-bottom: 64px; /* space between last page element and footer */
|
||||
|
||||
/* z-index */
|
||||
--z-index-modal: 1001; /* modal dialog, hard-coded from Fomantic modal.css */
|
||||
--z-index-toast: 1002; /* should be larger than modal */
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1200px) {
|
||||
|
@@ -20,7 +20,7 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ui.dimmer > * {
|
||||
.ui.dimmer > .ui.modal {
|
||||
position: static;
|
||||
margin-top: auto !important;
|
||||
margin-bottom: auto !important;
|
||||
|
@@ -3,7 +3,7 @@
|
||||
position: fixed;
|
||||
opacity: 0;
|
||||
transition: all .2s ease;
|
||||
z-index: 500;
|
||||
z-index: var(--z-index-toast);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 8px 24px var(--color-shadow);
|
||||
display: flex;
|
||||
|
@@ -525,7 +525,7 @@ $.fn.dropdown = function(parameters) {
|
||||
return true;
|
||||
}
|
||||
if(settings.onShow.call(element) !== false) {
|
||||
settings.onAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items
|
||||
$module.fomanticExt.onDropdownAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items
|
||||
module.animate.show(function() {
|
||||
if( module.can.click() ) {
|
||||
module.bind.intent();
|
||||
@@ -753,7 +753,7 @@ $.fn.dropdown = function(parameters) {
|
||||
if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) {
|
||||
module.show();
|
||||
}
|
||||
settings.onAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items
|
||||
$module.fomanticExt.onDropdownAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items
|
||||
}
|
||||
;
|
||||
if(settings.useLabels && module.has.maxSelections()) {
|
||||
@@ -3994,8 +3994,6 @@ $.fn.dropdown.settings = {
|
||||
onShow : function(){},
|
||||
onHide : function(){},
|
||||
|
||||
onAfterFiltered: function(){}, // GITEA-PATCH: callback to correctly handle the filtered items
|
||||
|
||||
/* Component */
|
||||
name : 'Dropdown',
|
||||
namespace : 'dropdown',
|
||||
|
@@ -467,7 +467,7 @@ $.fn.modal = function(parameters) {
|
||||
ignoreRepeatedEvents = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
$module.fomanticExt.onModalBeforeHidden.call(element); // GITEA-PATCH: handle more UI updates before hidden
|
||||
if( module.is.animating() || module.is.active() ) {
|
||||
if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
|
||||
module.remove.active();
|
||||
@@ -641,7 +641,7 @@ $.fn.modal = function(parameters) {
|
||||
$module
|
||||
.off('mousedown' + elementEventNamespace)
|
||||
;
|
||||
}
|
||||
}
|
||||
$dimmer
|
||||
.off('mousedown' + elementEventNamespace)
|
||||
;
|
||||
@@ -877,7 +877,7 @@ $.fn.modal = function(parameters) {
|
||||
? $(document).scrollTop() + settings.padding
|
||||
: $(document).scrollTop() + (module.cache.contextHeight - module.cache.height - settings.padding),
|
||||
marginLeft: -(module.cache.width / 2)
|
||||
})
|
||||
})
|
||||
;
|
||||
} else {
|
||||
$module
|
||||
@@ -886,7 +886,7 @@ $.fn.modal = function(parameters) {
|
||||
? -(module.cache.height / 2)
|
||||
: settings.padding / 2,
|
||||
marginLeft: -(module.cache.width / 2)
|
||||
})
|
||||
})
|
||||
;
|
||||
}
|
||||
module.verbose('Setting modal offset for legacy mode');
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import {request} from '../modules/fetch.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {hideToastsAll, showErrorToast} from '../modules/toast.ts';
|
||||
import {addDelegatedEventListener, submitEventSubmitter} from '../utils/dom.ts';
|
||||
import {confirmModal} from './comp/ConfirmModal.ts';
|
||||
import type {RequestOpts} from '../types.ts';
|
||||
@@ -24,6 +24,7 @@ function fetchActionDoRedirect(redirect: string) {
|
||||
|
||||
async function fetchActionDoRequest(actionElem: HTMLElement, url: string, opt: RequestOpts) {
|
||||
try {
|
||||
hideToastsAll();
|
||||
const resp = await request(url, opt);
|
||||
if (resp.status === 200) {
|
||||
let {redirect} = await resp.json();
|
||||
@@ -35,7 +36,9 @@ async function fetchActionDoRequest(actionElem: HTMLElement, url: string, opt: R
|
||||
window.location.reload();
|
||||
}
|
||||
return;
|
||||
} else if (resp.status >= 400 && resp.status < 500) {
|
||||
}
|
||||
|
||||
if (resp.status >= 400 && resp.status < 500) {
|
||||
const data = await resp.json();
|
||||
// the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
|
||||
// but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.
|
||||
|
@@ -9,9 +9,9 @@ const fomanticDropdownFn = $.fn.dropdown;
|
||||
// use our own `$().dropdown` function to patch Fomantic's dropdown module
|
||||
export function initAriaDropdownPatch() {
|
||||
if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once');
|
||||
$.fn.dropdown.settings.onAfterFiltered = onAfterFiltered;
|
||||
$.fn.dropdown = ariaDropdownFn;
|
||||
$.fn.fomanticExt.onResponseKeepSelectedItem = onResponseKeepSelectedItem;
|
||||
$.fn.fomanticExt.onDropdownAfterFiltered = onDropdownAfterFiltered;
|
||||
(ariaDropdownFn as FomanticInitFunction).settings = fomanticDropdownFn.settings;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ function updateSelectionLabel(label: HTMLElement) {
|
||||
}
|
||||
}
|
||||
|
||||
function onAfterFiltered(this: any) {
|
||||
function onDropdownAfterFiltered(this: any) {
|
||||
const $dropdown = $(this).closest('.ui.dropdown'); // "this" can be the "ui dropdown" or "<select>"
|
||||
const hideEmptyDividers = $dropdown.dropdown('setting', 'hideDividers') === 'empty';
|
||||
const itemsMenu = $dropdown[0].querySelector('.scrolling.menu') || $dropdown[0].querySelector('.menu');
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import $ from 'jquery';
|
||||
import type {FomanticInitFunction} from '../../types.ts';
|
||||
import {queryElems} from '../../utils/dom.ts';
|
||||
import {hideToastsFrom} from '../toast.ts';
|
||||
|
||||
const fomanticModalFn = $.fn.modal;
|
||||
|
||||
@@ -7,6 +9,7 @@ const fomanticModalFn = $.fn.modal;
|
||||
export function initAriaModalPatch() {
|
||||
if ($.fn.modal === ariaModalFn) throw new Error('initAriaModalPatch could only be called once');
|
||||
$.fn.modal = ariaModalFn;
|
||||
$.fn.fomanticExt.onModalBeforeHidden = onModalBeforeHidden;
|
||||
(ariaModalFn as FomanticInitFunction).settings = fomanticModalFn.settings;
|
||||
}
|
||||
|
||||
@@ -27,3 +30,10 @@ function ariaModalFn(this: any, ...args: Parameters<FomanticInitFunction>) {
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function onModalBeforeHidden(this: any) {
|
||||
const $modal = $(this);
|
||||
const elModal = $modal[0];
|
||||
queryElems(elModal, 'form', (form: HTMLFormElement) => form.reset());
|
||||
hideToastsFrom(elModal.closest('.ui.dimmer') ?? document.body);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import {htmlEscape} from 'escape-goat';
|
||||
import {svg} from '../svg.ts';
|
||||
import {animateOnce, showElem} from '../utils/dom.ts';
|
||||
import {animateOnce, queryElems, showElem} from '../utils/dom.ts';
|
||||
import Toastify from 'toastify-js'; // don't use "async import", because when network error occurs, the "async import" also fails and nothing is shown
|
||||
import type {Intent} from '../types.ts';
|
||||
import type {SvgName} from '../svg.ts';
|
||||
@@ -37,17 +37,20 @@ const levels: ToastLevels = {
|
||||
|
||||
type ToastOpts = {
|
||||
useHtmlBody?: boolean,
|
||||
preventDuplicates?: boolean,
|
||||
preventDuplicates?: boolean | string,
|
||||
} & Options;
|
||||
|
||||
type ToastifyElement = HTMLElement & {_giteaToastifyInstance?: Toast };
|
||||
|
||||
// See https://github.com/apvarun/toastify-js#api for options
|
||||
function showToast(message: string, level: Intent, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}): Toast {
|
||||
const body = useHtmlBody ? String(message) : htmlEscape(message);
|
||||
const key = `${level}-${body}`;
|
||||
const parent = document.querySelector('.ui.dimmer.active') ?? document.body;
|
||||
const duplicateKey = preventDuplicates ? (preventDuplicates === true ? `${level}-${body}` : preventDuplicates) : '';
|
||||
|
||||
// prevent showing duplicate toasts with same level and message, and give a visual feedback for end users
|
||||
// prevent showing duplicate toasts with the same level and message, and give visual feedback for end users
|
||||
if (preventDuplicates) {
|
||||
const toastEl = document.querySelector(`.toastify[data-toast-unique-key="${CSS.escape(key)}"]`);
|
||||
const toastEl = parent.querySelector(`:scope > .toastify.on[data-toast-unique-key="${CSS.escape(duplicateKey)}"]`);
|
||||
if (toastEl) {
|
||||
const toastDupNumEl = toastEl.querySelector('.toast-duplicate-number');
|
||||
showElem(toastDupNumEl);
|
||||
@@ -59,6 +62,7 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
|
||||
|
||||
const {icon, background, duration: levelDuration} = levels[level ?? 'info'];
|
||||
const toast = Toastify({
|
||||
selector: parent,
|
||||
text: `
|
||||
<div class='toast-icon'>${svg(icon)}</div>
|
||||
<div class='toast-body'><span class="toast-duplicate-number tw-hidden">1</span>${body}</div>
|
||||
@@ -74,7 +78,8 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
|
||||
|
||||
toast.showToast();
|
||||
toast.toastElement.querySelector('.toast-close').addEventListener('click', () => toast.hideToast());
|
||||
toast.toastElement.setAttribute('data-toast-unique-key', key);
|
||||
toast.toastElement.setAttribute('data-toast-unique-key', duplicateKey);
|
||||
(toast.toastElement as ToastifyElement)._giteaToastifyInstance = toast;
|
||||
return toast;
|
||||
}
|
||||
|
||||
@@ -89,3 +94,15 @@ export function showWarningToast(message: string, opts?: ToastOpts): Toast {
|
||||
export function showErrorToast(message: string, opts?: ToastOpts): Toast {
|
||||
return showToast(message, 'error', opts);
|
||||
}
|
||||
|
||||
function hideToastByElement(el: Element): void {
|
||||
(el as ToastifyElement)?._giteaToastifyInstance?.hideToast();
|
||||
}
|
||||
|
||||
export function hideToastsFrom(parent: Element): void {
|
||||
queryElems(parent, ':scope > .toastify.on', hideToastByElement);
|
||||
}
|
||||
|
||||
export function hideToastsAll(): void {
|
||||
queryElems(document, '.toastify.on', hideToastByElement);
|
||||
}
|
||||
|
Reference in New Issue
Block a user