improve UI/UX of the User Area

#496
This commit is contained in:
Hamid Reza Mohammadi 2026-08-05 13:58:27 +03:30
parent 989defdf62
commit 2fee534f11
4 changed files with 191 additions and 341 deletions

View File

@ -1,45 +1,43 @@
@page
@page
@model GanjooRazor.Areas.User.Pages.HistoryModel
@using DNTPersianUtils.Core
@{
Layout = "_UserPanelLayout";
ViewData["Title"] = "ادامهٔ مطالعه";
await GanjooRazor.Utils.GanjoorSessionChecker.ApplyPermissionsToViewData(Request, Response, ViewData);
string color = "gray";
}
<h1>@ViewData["Title"]</h1>
<div class="up-page-header">
<h1>@ViewData["Title"]</h1>
</div>
@if (Model.LastError != "")
{
<h2>@Model.LastError</h2>
<div class="up-alert up-alert--danger">@Model.LastError</div>
}
else
{
<script>
function deleteHistoryItem(historyItemId) {
if (!confirm('آیا از حذف این مورد اطمینان دارید؟'))
return;
var url = '?handler=HistoryItem';
async function deleteHistoryItem(historyItemId) {
var ok = await upConfirm('آیا از حذف این مورد اطمینان دارید؟');
if (!ok) return;
$.ajax({
type: "DELETE",
url: url,
data: {
id: historyItemId
url: '?handler=HistoryItem',
data: { id: historyItemId },
error: function (e) {
upToast(e.responseText || 'حذف مورد با خطا مواجه شد.', 'error');
},
success: function () {
var notificationBlockId = '#history-item-' + historyItemId;
$(notificationBlockId).remove();
$('#history-item-' + historyItemId).remove();
upToast('مورد حذف شد.', 'success');
},
});
}
function switchTracking(start) {
if(start == false){
if (!confirm('آیا از توقف ذخیرهٔ تاریخچه و حذف تاریخچهٔ بازدیدها اطمینان دارید؟'))
{
return;
}
async function switchTracking(start) {
if (start == false) {
var ok = await upConfirm('آیا از توقف ذخیرهٔ تاریخچه و حذف تاریخچهٔ بازدیدها اطمینان دارید؟');
if (!ok) return;
document.getElementById('switch-tracking-button').style.visibility = 'hidden';
document.getElementById('processing').innerHTML = 'در حال پردازش ...';
@ -47,8 +45,8 @@ else
$.ajax({
type: "POST",
url: '?handler=StopTracking',
error: function() {
alert('خطا رخ داد.');
error: function () {
upToast('خطا رخ داد.', 'error');
location.reload();
},
success: function () {
@ -64,8 +62,8 @@ else
$.ajax({
type: "POST",
url: '?handler=StartTracking',
error: function() {
alert('خطا رخ داد.');
error: function () {
upToast('خطا رخ داد.', 'error');
location.reload();
},
success: function () {
@ -74,120 +72,61 @@ else
});
}
</script>
<p style="text-align:center">
@foreach (var paginationLink in Model.PaginationLinks)
{
if (!string.IsNullOrEmpty(paginationLink.Url))
{
<a href="@paginationLink.Url">@paginationLink.Name</a>
}
else
{
@paginationLink.Name
}
}
</p>
<div class="related-images-frame">
<partial name="_Pagination" model="Model.PaginationLinks" />
<div class="up-card">
@if (Model.TrackingIsEnabled)
{
<p style="text-align:center; color: green">
ذخیرهٔ تاریخچهٔ بازدیدهای شما فعال است.
</p>
<p>
<a role="button" onclick="switchTracking(false)" id="switch-tracking-button" class="pagebutton">توقف ذخیرهٔ تاریخچه</a>
</p>
<p id="processing">
</p>
<p>
<span style="color:red">تذکر مهم: </span> توقف ذخیرهٔ تاریخچه باعث حذف تاریخچهٔ ذخیره شده تا به حال می شود.
</p>
<div class="up-alert up-alert--success up-text-center">ذخیرهٔ تاریخچهٔ بازدیدهای شما فعال است.</div>
<div class="up-form-actions up-text-center" style="justify-content:center;">
<a role="button" onclick="switchTracking(false)" id="switch-tracking-button" class="up-btn up-btn--danger">توقف ذخیرهٔ تاریخچه</a>
</div>
<p id="processing" class="up-text-center"></p>
<p class="up-hint up-hint--danger up-text-center">تذکر مهم: توقف ذخیرهٔ تاریخچه باعث حذف تاریخچهٔ ذخیره‌شده تا به حال می‌شود.</p>
}
else
{
<p>
ذخیرهٔ تاریخچهٔ بازدیدهای شما غیرفعال است.
</p>
<p>
<a role="button" onclick="switchTracking(true)" id="switch-tracking-button" class="pagebutton">فعالسازی ذخیرهٔ تاریخچه</a>
</p>
<p id="processing">
</p>
<p>
تاریخچهٔ بازیدهای شما فقط به خود شما نمایش داده می‌شود و از آن استفادهٔ دیگری نمی‌شود. هر زمان که بخواهید می‌توانید ذخیرهٔ تاریخچه را متوقف کنید و تاریخچهٔ بازدیدهایتان را پاک کنید.
</p>
<p class="up-text-center">ذخیرهٔ تاریخچهٔ بازدیدهای شما غیرفعال است.</p>
<div class="up-form-actions up-text-center" style="justify-content:center;">
<a role="button" onclick="switchTracking(true)" id="switch-tracking-button" class="up-btn up-btn--success">فعال‌سازی ذخیرهٔ تاریخچه</a>
</div>
<p id="processing" class="up-text-center"></p>
<p class="up-hint up-text-center">تاریخچهٔ بازدیدهای شما فقط به خود شما نمایش داده می‌شود و از آن استفادهٔ دیگری نمی‌شود. هر زمان که بخواهید می‌توانید ذخیرهٔ تاریخچه را متوقف کنید و تاریخچهٔ بازدیدهایتان را پاک کنید.</p>
}
<p>
<span style="color:red">نکته: </span> اگر روی دستگاههای متعدد به گنجور وارد شده‌اید توقف ذخیرهٔ تاریخچه روی همهٔ آنها اعمال می‌شود اما فعالسازی ذخیرهٔ تاریخچه مستلزم آن است که روی هر دستگاه دیگری غیر از دستگاهی که فعالسازی روی آن انجام شده یک بار از این صفحه بازدید کنید یا از حساب کاربری خود در گنجور خارج و مجدداً به آن وارد شوید.
</p>
<p class="up-hint up-text-center">نکته: اگر روی دستگاه‌های متعدد به گنجور وارد شده‌اید، توقف ذخیرهٔ تاریخچه روی همهٔ آنها اعمال می‌شود اما فعال‌سازی ذخیرهٔ تاریخچه مستلزم آن است که روی هر دستگاه دیگری غیر از دستگاهی که فعال‌سازی روی آن انجام شده یک‌بار از این صفحه بازدید کنید یا از حساب کاربری خود در گنجور خارج و مجدداً به آن وارد شوید.</p>
</div>
@if (Model.HistoryItems.Count == 0)
{
<partial name="_EmptyState" model="@("هنوز آیتمی در تاریخچهٔ مطالعهٔ شما ثبت نشده است.")" />
}
else
{
<div class="up-list">
@foreach (var historyItem in Model.HistoryItems)
{
if (color == "gray")
color = "lightsteelblue";
else
color = "gray";
<div style="background-color:@color" id="history-item-@historyItem.Id">
<blockquote>
<p>
<a href="@historyItem.PoemFullUrl">@historyItem.PoemFullTitle</a> <small>در @historyItem.DateTime.ToFriendlyPersianDateTextify()</small>
</p>
<div class="up-list-item" id="history-item-@historyItem.Id">
<div class="up-list-item__meta">
<a href="@historyItem.PoemFullUrl">@historyItem.PoemFullTitle</a> در @historyItem.DateTime.ToFriendlyPersianDateTextify()
</div>
<a href="@historyItem.PoemFullUrl">
<img src="@historyItem.PoetImageUrl" style="width:unset;display:unset;border:unset" alt="@historyItem.PoetName" />
</a>
<div id="historyItem-text-@historyItem.Id">
<p>
@historyItem.VerseText
</p>
<p>@historyItem.VerseText</p>
@if (!string.IsNullOrEmpty(historyItem.Verse2Text))
{
<p>
@historyItem.Verse2Text
</p>
<p>@historyItem.Verse2Text</p>
}
</div>
</blockquote>
<p>
<a role="button" onclick="deleteHistoryItem('@historyItem.Id')" class="actionlink" title="حذف"><i class="noindent-info-button delete-icon"></i></a>
</p>
<div class="up-list-item__actions">
<a role="button" onclick="deleteHistoryItem('@historyItem.Id')" class="up-icon-btn" title="حذف" aria-label="حذف این مورد"><i class="noindent-info-button delete-icon"></i></a>
</div>
</div>
}
</div>
<hr />
}
<p style="text-align:center">
@foreach (var paginationLink in Model.PaginationLinks)
{
if (!string.IsNullOrEmpty(paginationLink.Url))
{
<a href="@paginationLink.Url">@paginationLink.Name</a>
}
else
{
@paginationLink.Name
}
}
</p>
<partial name="_Pagination" model="Model.PaginationLinks" />
}

View File

@ -1,35 +1,35 @@
@page
@page
@model GanjooRazor.Areas.User.Pages.MyBookmarksModel
@using DNTPersianUtils.Core
@{
Layout = "_UserPanelLayout";
ViewData["Title"] = "نشان‌شده‌های من";
await GanjooRazor.Utils.GanjoorSessionChecker.ApplyPermissionsToViewData(Request, Response, ViewData);
string color = "gray";
}
<h1>@ViewData["Title"]</h1>
<div class="up-page-header">
<h1>@ViewData["Title"]</h1>
</div>
@if (Model.LastError != "")
{
<h2>@Model.LastError</h2>
<div class="up-alert up-alert--danger">@Model.LastError</div>
}
else
{
<script>
function deleteBookmark(bookmarkId) {
if (!confirm('آیا از حذف این نشان‌شده اطمینان دارید؟'))
return;
var url = '?handler=Bookmark';
async function deleteBookmark(bookmarkId) {
var ok = await upConfirm('آیا از حذف این نشان‌شده اطمینان دارید؟');
if (!ok) return;
$.ajax({
type: "DELETE",
url: url,
data: {
id: bookmarkId
url: '?handler=Bookmark',
data: { id: bookmarkId },
error: function (e) {
upToast(e.responseText || 'حذف نشان‌شده با خطا مواجه شد.', 'error');
},
success: function() {
var notificationBlockId = '#bookmark-' + bookmarkId;
$(notificationBlockId).remove();
success: function () {
$('#bookmark-' + bookmarkId).remove();
upToast('نشان‌شده حذف شد.', 'success');
},
});
}
@ -42,130 +42,88 @@ else
}
function editPrivateNoteFinal() {
$("#editnoteform").unbind('submit').bind('submit', function(e) {
e.preventDefault(); // avoid to execute the actual submit of the form.
var url = '?handler=BookmarkNote';
$("#editnoteform").unbind('submit').bind('submit', function (e) {
e.preventDefault();
var bookmarkId = $("#editbookmarkId").val();
$.ajax({
type: "PUT",
url: url,
url: '?handler=BookmarkNote',
data: {
id: bookmarkId,
note: $("textarea#editNoteText").val()
},
success: function() {
document.getElementById('bookmark-note-dialog').style.display = 'none';
var noteTextBlockId = '#note-text-' + bookmarkId;
$(noteTextBlockId).html($("textarea#editNoteText").val());
error: function (e) {
upToast(e.responseText || 'ثبت یادداشت با خطا مواجه شد.', 'error');
},
success: function () {
document.getElementById('bookmark-note-dialog').style.display = 'none';
var noteTextBlockId = '#note-text-' + bookmarkId;
$(noteTextBlockId).html($("textarea#editNoteText").val());
upToast('یادداشت ثبت شد.', 'success');
},
});
});
}
</script>
<p style="text-align:center">
@foreach (var paginationLink in Model.PaginationLinks)
{
if (!string.IsNullOrEmpty(paginationLink.Url))
{
<a href="@paginationLink.Url">@paginationLink.Name</a>
}
else
{
@paginationLink.Name
}
}
</p>
<partial name="_Pagination" model="Model.PaginationLinks" />
<div class="up-card">
<form method="get" id="searchcommentsform" class="up-form-actions">
<input type="text" name="w" id="w" value="@Model.Query" placeholder="جستجو در یادداشت‌ها" style="flex:1;min-width:200px;" />
<button type="submit" class="up-btn up-btn--primary">جستجو</button>
</form>
</div>
@if (Model.Bookmarks.Count == 0)
{
<p>
شما هنوز هیچ بیتی را نشان نکرده‌اید. برای آشنایی با نحوهٔ نشان کردن اشعار <a href="https://blog.ganjoor.net/1400/07/08/ganjoor-bookmarking/" target="_blank">اینجا</a> را ببینید.
</p>
<partial name="_EmptyState" model="@("شما هنوز هیچ بیتی را نشان نکرده‌اید. برای آشنایی با نحوهٔ نشان کردن اشعار <a href=\"https://blog.ganjoor.net/1400/07/08/ganjoor-bookmarking/\" target=\"_blank\">اینجا</a> را ببینید.")" />
}
<div class="sitem actionbox">
<div class="related-images-frame padding-20px">
<form method="get" id="searchcommentsform">
<input type="text" name="w" id="w" class="width-100per" value="@Model.Query" placeholder="جستجو در یادداشتها" />
<input type="submit" class="width-100per margin-top-5px" value="جستجو در یادداشتها" />
</form>
</div><!-- related-images-frame -->
</div> <!-- sitem actionbo -->
else
{
<div class="up-list">
@foreach (var bookmark in Model.Bookmarks)
{
if (color == "gray")
color = "lightsteelblue";
else
color = "gray";
<div style="background-color:@color" id="bookmark-@bookmark.Id">
<blockquote>
<p>
از <a href="@($"{bookmark.PoemFullUrl}{(bookmark.CoupletIndex < 0 ? $"#comment-{-bookmark.CoupletIndex}" :$"#bn{bookmark.CoupletIndex + 1}")}")">@bookmark.PoemFullTitle</a> <small>در @bookmark.DateTime.ToFriendlyPersianDateTextify()</small>
</p>
<a href="@($"{bookmark.PoemFullUrl}{(bookmark.CoupletIndex < 0 ? $"#comment-{-bookmark.CoupletIndex}" :$"#bn{bookmark.CoupletIndex + 1}")}")">
var bookmarkUrl = $"{bookmark.PoemFullUrl}{(bookmark.CoupletIndex < 0 ? $"#comment-{-bookmark.CoupletIndex}" : $"#bn{bookmark.CoupletIndex + 1}")}";
<div class="up-list-item" id="bookmark-@bookmark.Id">
<div class="up-list-item__meta">
از <a href="@bookmarkUrl">@bookmark.PoemFullTitle</a> در @bookmark.DateTime.ToFriendlyPersianDateTextify()
</div>
<a href="@bookmarkUrl">
<img src="@bookmark.PoetImageUrl" style="width:unset;display:unset;border:unset" alt="@bookmark.PoetName" />
</a>
<div id="bookmark-text-@bookmark.Id">
<p>
@bookmark.VerseText
</p>
<p>@bookmark.VerseText</p>
@if (!string.IsNullOrEmpty(bookmark.Verse2Text))
{
<p>
@bookmark.Verse2Text
</p>
<p>@bookmark.Verse2Text</p>
}
</div>
@if (!string.IsNullOrEmpty(bookmark.PrivateNote))
{
<span class="red-color">یادداشت:</span>
<div>
<span class="up-badge">یادداشت</span>
<div id="note-text-@bookmark.Id" class="ganjoor-comment">
@Html.Raw(bookmark.PrivateNote)
</div>
</div>
}
else
{
<div id="note-text-@bookmark.Id">
<div id="note-text-@bookmark.Id"></div>
}
<div class="up-list-item__actions">
<a role="button" onclick="deleteBookmark('@bookmark.Id')" class="up-icon-btn" title="حذف" aria-label="حذف این نشان‌شده"><i class="noindent-info-button delete-icon"></i></a>
<a role="button" onclick="editPrivateNote('@bookmark.Id')" class="up-icon-btn" title="یادداشت شخصی" aria-label="ویرایش یادداشت شخصی"><i class="noindent-info-button edit-poem-button"></i></a>
</div>
</div>
}
</blockquote>
<p>
<a role="button" onclick="deleteBookmark('@bookmark.Id')" class="actionlink" title="حذف"><i class="noindent-info-button delete-icon"></i></a>
</p>
<p>
<a role="button" onclick="editPrivateNote('@bookmark.Id')" class="actionlink" title="یادداشت شخصی"><i class="noindent-info-button edit-poem-button"></i></a>
</p>
</div>
<hr />
}
<p style="text-align:center">
@foreach (var paginationLink in Model.PaginationLinks)
{
if (!string.IsNullOrEmpty(paginationLink.Url))
{
<a href="@paginationLink.Url">@paginationLink.Name</a>
}
else
{
@paginationLink.Name
}
}
</p>
<partial name="_Pagination" model="Model.PaginationLinks" />
<div id="bookmark-note-dialog" class="modal">
<span onclick="document.getElementById('bookmark-note-dialog').style.display='none'"
@ -185,13 +143,10 @@ else
</tr>
<tr>
<td>
یادداشتهایی که روی نشان‌شده‌ها می‌نویسید خصوصی هستند و با حذف نشان‌شده پاک می‌شوند.
یادداشت‌هایی که روی نشان‌شده‌ها می‌نویسید خصوصی هستند و با حذف نشان‌شده پاک می‌شوند.
</td>
</tr>
</table>
</form>
</div>
}

View File

@ -1,45 +1,37 @@
@page
@page
@model GanjooRazor.Areas.User.Pages.MyCommentsModel
@using DNTPersianUtils.Core
@{
Layout = "_UserPanelLayout";
ViewData["Title"] = "حاشیه‌های من";
await GanjooRazor.Utils.GanjoorSessionChecker.ApplyPermissionsToViewData(Request, Response, ViewData);
string color = "gray";
}
<h1>@ViewData["Title"]</h1>
<div class="up-page-header">
<h1>@ViewData["Title"]</h1>
</div>
@if (Model.LastError != "")
{
<h2>@Model.LastError</h2>
<div class="up-alert up-alert--danger">@Model.LastError</div>
}
else
{
<script>
function deleteThisComment(commentId) {
if (!confirm('آیا از حذف این حاشیه اطمینان دارید؟'))
return;
var url = '?handler=MyComment';
async function deleteThisComment(commentId) {
var ok = await upConfirm('آیا از حذف این حاشیه اطمینان دارید؟');
if (!ok) return;
$.ajax({
type: "DELETE",
url: url,
data: {
id: commentId
},
url: '?handler=MyComment',
data: { id: commentId },
error: function (e) {
if (e.responseText == null)
alert(e);
else
alert(e.responseText);
upToast(e.responseText || 'حذف حاشیه با خطا مواجه شد.', 'error');
},
success: function () {
var commentBlockId = '#comment-' + commentId;
$(commentBlockId).remove();
$('#comment-' + commentId).remove();
upToast('حاشیه حذف شد.', 'success');
},
});
}
function editThisComment(commentId) {
@ -51,99 +43,59 @@ else
function editCommentFinal() {
$("#editcommentform").unbind('submit').bind('submit', function (e) {
e.preventDefault(); // avoid to execute the actual submit of the form.
var url = '?handler=MyComment';
e.preventDefault();
var commentId = $("#editCommentId").val();
$.ajax({
type: "PUT",
url: url,
url: '?handler=MyComment',
data: {
id: commentId,
comment: $("textarea#editCommentText").val()
},
error: function (e) {
if (e.responseText == null)
alert(e);
else
alert(e.responseText);
upToast(e.responseText || 'ویرایش حاشیه با خطا مواجه شد.', 'error');
},
success: function () {
document.getElementById('id02').style.display = 'none';
var commentTextBlockId = '#comment-text-' + commentId;
$(commentTextBlockId).html($("textarea#editCommentText").val());
upToast('حاشیه ویرایش شد.', 'success');
},
});
});
}
</script>
<p style="text-align:center">
@foreach (var paginationLink in Model.PaginationLinks)
{
if (!string.IsNullOrEmpty(paginationLink.Url))
{
<a href="@paginationLink.Url">@paginationLink.Name</a>
}
else
{
@paginationLink.Name
}
}
</p>
<partial name="_Pagination" model="Model.PaginationLinks" />
@if (Model.Comments.Count == 0)
{
<p>
حاشیه‌ای یافت نشد.
</p>
<partial name="_EmptyState" model="@("حاشیه‌ای یافت نشد.")" />
}
else
{
<div class="up-list">
@foreach (var comment in Model.Comments)
{
if (color == "gray")
color = "lightsteelblue";
else
color = "gray";
<div style="background-color:@color" id="comment-@comment.Id">
<blockquote>
<div class="up-list-item" id="comment-@comment.Id">
<div id="comment-text-@comment.Id">
@Html.Raw(comment.HtmlComment)
</div>
<p>
@Model.UserInfo.NickName <small>در @comment.CommentDate.ToFriendlyPersianDateTextify()</small> دربارهٔ <a href="@comment.Poem.UrlSlug#comment-@comment.Id">@comment.Poem.Title</a>
</p>
</blockquote>
<p>
<a role="button" onclick="deleteThisComment(@comment.Id)" class="actionlink" title="حذف حاشیه"><i class="noindent-info-button delete-icon"></i></a>
<a role="button" onclick="editThisComment(@comment.Id)" class="actionlink" title="ویرایش حاشیه"><i class="noindent-info-button edit-poem-button"></i></a>
</p>
<div class="up-list-item__meta">
@Model.UserInfo.NickName در @comment.CommentDate.ToFriendlyPersianDateTextify() دربارهٔ <a href="@comment.Poem.UrlSlug#comment-@comment.Id">@comment.Poem.Title</a>
</div>
<div class="up-list-item__actions">
<a role="button" onclick="deleteThisComment(@comment.Id)" class="up-icon-btn" title="حذف حاشیه" aria-label="حذف این حاشیه"><i class="noindent-info-button delete-icon"></i></a>
<a role="button" onclick="editThisComment(@comment.Id)" class="up-icon-btn" title="ویرایش حاشیه" aria-label="ویرایش این حاشیه"><i class="noindent-info-button edit-poem-button"></i></a>
</div>
</div>
}
</div>
<hr />
}
<p style="text-align:center">
@foreach (var paginationLink in Model.PaginationLinks)
{
if (!string.IsNullOrEmpty(paginationLink.Url))
{
<a href="@paginationLink.Url">@paginationLink.Name</a>
}
else
{
@paginationLink.Name
}
}
</p>
<partial name="_Pagination" model="Model.PaginationLinks" />
<div id="id02" class="modal">
<span onclick="document.getElementById('id02').style.display='none'"

View File

@ -3,10 +3,14 @@
<p>. Pass the explanatory text as the model:
<partial name="_EmptyState" model="@("در حال حاضر اعلانی وجود ندارد.")" />
The message is rendered with Html.Raw, so simple trusted inline markup
(e.g. a <a> link) is fine - only pass hardcoded, developer-authored
strings here, never raw user input.
*@
@model string
<div class="up-empty">
<div class="up-empty__icon" aria-hidden="true">&#9679;</div>
<p>@Model</p>
<p>@Html.Raw(Model)</p>
</div>