editor service errors are displayed better

This commit is contained in:
Hamid Reza Mohammadi 2026-10-03 08:17:31 +03:30
parent 5ce8973ca3
commit 0c98df8526
5 changed files with 125 additions and 14 deletions

View File

@ -1007,7 +1007,7 @@
location.reload(true);
},
error: function (e) {
upToast(e.responseText || String(e), 'error');
upShowError(e.responseText || String(e));
$('#savecorrections').show();
$('#saving').text('');
}
@ -1024,7 +1024,7 @@
id: poemId
},
error: function (e) {
upToast(e.responseText || String(e), 'error');
upShowError(e.responseText || String(e));
},
success: function (rhyme) {
$(elementId).val(rhyme);
@ -1049,7 +1049,7 @@
poemid: id,
},
error: function (e) {
upToast(e.responseText || 'خطا رخ داد.', 'error');
upShowError(e.responseText || 'خطا رخ داد.');
},
success: function () {
location.reload(true);
@ -1074,7 +1074,7 @@
},
error: function (e) {
upToast(e.responseText || String(e), 'error');
upShowError(e.responseText || String(e));
},
success: function () {
location.reload(true);
@ -1161,7 +1161,7 @@
location.href = '/?p=' + String(id);
},
error: function (e) {
upToast(e.responseText || String(e), 'error');
upShowError(e.responseText || String(e));
$('#breakpoem-' + String(vOrder)).show();
}
@ -1182,7 +1182,7 @@
upToast('انجام شد.');
},
error: function (e) {
upToast(e.responseText || String(e), 'error');
upShowError(e.responseText || String(e));
}
});
@ -1210,7 +1210,7 @@
id: id,
},
error: function (e) {
upToast(e.responseText || String(e), 'error');
upShowError(e.responseText || String(e));
},
success: function () {
upToast('انجام شد.');
@ -1235,7 +1235,7 @@
id: id,
},
error: function (e) {
upToast(e.responseText || String(e), 'error');
upShowError(e.responseText || String(e));
},
success: function () {
upToast('انجام شد.');
@ -1268,7 +1268,7 @@
location.reload();
},
error: function (e) {
upToast(e.responseText || String(e), 'error');
upShowError(e.responseText || String(e));
}
});
@ -1289,7 +1289,7 @@
location.reload();
},
error: function (e) {
upToast(e.responseText || String(e), 'error');
upShowError(e.responseText || String(e));
}
});

View File

@ -0,0 +1,14 @@
@* Styled, non-auto-dismissing display for a service error's full text (these often carry the
backend's exception message and stack trace, which upToast's few-second toast never gave
anyone enough time to actually read before it faded out). Included once by
_UserPanelLayout.cshtml. Trigger with JS: upShowError(message) *@
<div class="up-modal-backdrop" id="upErrorBackdrop" hidden>
<div class="up-modal up-modal--error" role="alertdialog" aria-modal="true" aria-labelledby="upErrorTitle">
<p id="upErrorTitle" class="up-modal__message">خطایی در ارتباط با سرویس رخ داد:</p>
<pre id="upErrorDetails" class="up-modal__details"></pre>
<div class="up-modal__actions">
<button type="button" class="up-btn up-btn--ghost" id="upErrorCopy">کپی متن خطا</button>
<button type="button" class="up-btn up-btn--danger" id="upErrorClose">بستن</button>
</div>
</div>
</div>

View File

@ -205,10 +205,11 @@
<partial name="_Toasts" />
<partial name="_ConfirmModal" />
<partial name="_ErrorModal" />
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
<script src="/js/user-panel.js?version=3"></script>
<script src="/js/user-panel.js?version=4"></script>
@await RenderSectionAsync("Scripts", required: false)
<div id="footer"></div>

View File

@ -475,6 +475,33 @@ body.up-modal-open {
overflow: hidden;
}
/* ---------- error modal (persistent service-error display, see _ErrorModal.cshtml) ---------- */
.up-modal--error {
max-width: 700px;
text-align: start;
}
.up-modal__details {
max-height: 50vh;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
/* backend error text/stack traces are English - keep them LTR regardless of the page's own
RTL direction, same reasoning as .linputText elsewhere in this file */
direction: ltr;
text-align: left;
font-family: 'Consolas', 'Courier New', monospace;
font-size: 0.82rem;
line-height: 1.5;
color: var(--up-text);
background: var(--up-bg);
border: 1px solid var(--up-border);
border-radius: var(--up-radius-sm);
padding: var(--up-space-2);
margin: 0 0 var(--up-space-4);
}
/* ---------- dashboard quick links ---------- */
.up-quick-links {

View File

@ -1,8 +1,8 @@
// User area (پیشخان کاربر) shared UI helpers.
// Provides upConfirm() and upToast() as drop-in, styled replacements for
// Provides upConfirm(), upToast() and upShowError() as drop-in, styled replacements for
// window.confirm()/window.alert(), used across the panel's AJAX actions.
// Requires the markup from Areas/User/Pages/Shared/_ConfirmModal.cshtml and
// _Toasts.cshtml to be present on the page (included once by _UserPanelLayout).
// Requires the markup from Pages/Shared/_ConfirmModal.cshtml, _Toasts.cshtml and
// _ErrorModal.cshtml to be present on the page (included once by _UserPanelLayout).
/**
* Shows a styled confirmation dialog and resolves to true/false.
@ -88,3 +88,72 @@ function upToast(message, type) {
}, 250);
}, 4000);
}
/**
* Shows a service error's full text (often a backend exception message plus its stack trace) in
* a modal that stays on screen until closed, with a copy button - unlike upToast(), which fades
* out after 4 seconds and gives no way to get back the text it just showed. Falls back to
* upToast() if the modal markup isn't on the page.
* @param {string} message
*/
function upShowError(message) {
message = message == null ? '' : String(message);
var backdrop = document.getElementById('upErrorBackdrop');
var detailsEl = document.getElementById('upErrorDetails');
var closeBtn = document.getElementById('upErrorClose');
var copyBtn = document.getElementById('upErrorCopy');
if (!backdrop || !detailsEl || !closeBtn || !copyBtn) {
upToast(message, 'error');
return;
}
detailsEl.textContent = message;
backdrop.hidden = false;
document.body.classList.add('up-modal-open');
function cleanup() {
backdrop.hidden = true;
document.body.classList.remove('up-modal-open');
closeBtn.removeEventListener('click', onClose);
copyBtn.removeEventListener('click', onCopy);
backdrop.removeEventListener('click', onBackdropClick);
document.removeEventListener('keydown', onKeyDown);
}
function onClose() { cleanup(); }
function onBackdropClick(e) { if (e.target === backdrop) cleanup(); }
function onKeyDown(e) { if (e.key === 'Escape') cleanup(); }
function onCopy() {
var restoreText = copyBtn.textContent;
function showCopied(ok) {
copyBtn.textContent = ok ? '✓ کپی شد' : 'کپی ممکن نشد';
setTimeout(function () { copyBtn.textContent = restoreText; }, 1500);
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(message).then(function () { showCopied(true); }, function () { showCopied(false); });
} else {
// clipboard API unavailable (e.g. non-HTTPS context) - fall back to a temporary,
// off-screen textarea and the legacy execCommand copy path
var ta = document.createElement('textarea');
ta.value = message;
ta.style.position = 'fixed';
ta.style.left = '-9999px';
document.body.appendChild(ta);
ta.select();
var ok = false;
try { ok = document.execCommand('copy'); } catch (e) { ok = false; }
document.body.removeChild(ta);
showCopied(ok);
}
}
closeBtn.addEventListener('click', onClose);
copyBtn.addEventListener('click', onCopy);
backdrop.addEventListener('click', onBackdropClick);
document.addEventListener('keydown', onKeyDown);
closeBtn.focus();
}