From 9c93b4e51afba76d16adeb1940e88483eb8f28cc Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Thu, 11 Jun 2026 01:26:35 +0330 Subject: [PATCH] feat(gallery+editor): dedicated /gallery page, homepage teaser, in-content images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Homepage gallery: - Show only 3 before/after samples as a teaser (was: all items) - Add "مشاهده گالری کامل (N نمونه)" CTA when more than 3 exist - Remove the now-pointless category tabs from the teaser New /gallery page: - Full before/after grid with category filter tabs (deduped from data) - Responsive cards with قبل/بعد labels + captions, empty state - Added to sitemap.xml (priority 0.8) Blog content editor: - New 🖼 تصویر toolbar button inserts an uploaded image at the cursor (direct upload, no forced crop) — for richer post bodies - Responsive img styling on the public post page Note: the filler-lab-soorat cover not showing is a data issue — that post has an empty featuredImage in the DB (verified); re-upload + save fixes it. The upload/save path itself is correct. Co-Authored-By: Claude Opus 4.8 --- DrSousan.Api/Pages/Blog/Post.cshtml | 1 + DrSousan.Api/Pages/Gallery.cshtml | 105 ++++++++++++++++++++++++++ DrSousan.Api/Pages/Gallery.cshtml.cs | 36 +++++++++ DrSousan.Api/Pages/Index.cshtml | 13 ++-- DrSousan.Api/Pages/Index.cshtml.cs | 8 +- DrSousan.Api/Program.cs | 1 + DrSousan.Api/wwwroot/admin/index.html | 26 +++++++ 7 files changed, 181 insertions(+), 9 deletions(-) create mode 100644 DrSousan.Api/Pages/Gallery.cshtml create mode 100644 DrSousan.Api/Pages/Gallery.cshtml.cs diff --git a/DrSousan.Api/Pages/Blog/Post.cshtml b/DrSousan.Api/Pages/Blog/Post.cshtml index d71ab32..add8842 100644 --- a/DrSousan.Api/Pages/Blog/Post.cshtml +++ b/DrSousan.Api/Pages/Blog/Post.cshtml @@ -71,6 +71,7 @@ .article-content strong{color:var(--dark);font-weight:600} .article-content a{color:var(--gold);border-bottom:1px solid var(--gold-pale)} .article-content blockquote{border-right:4px solid var(--gold);padding:.8rem 1.2rem;background:var(--gold-pale);border-radius:0 8px 8px 0;margin:1.2rem 0;font-style:italic;color:var(--mid)} + .article-content img{max-width:100%;height:auto;border-radius:12px;margin:1.2rem 0;display:block} /* ─── Tags ─────────────────────────────────────────────────────── */ .article-tags{margin-top:2rem;padding-top:1.5rem;border-top:1px solid var(--border);display:flex;gap:.5rem;flex-wrap:wrap} .tag{background:var(--bg);border:1px solid var(--border);padding:.25rem .75rem;border-radius:50px;font-size:.78rem;color:var(--mid)} diff --git a/DrSousan.Api/Pages/Gallery.cshtml b/DrSousan.Api/Pages/Gallery.cshtml new file mode 100644 index 0000000..c8b0a8f --- /dev/null +++ b/DrSousan.Api/Pages/Gallery.cshtml @@ -0,0 +1,105 @@ +@page "/gallery" +@model DrSousan.Api.Pages.GalleryModel + +@section Head { + @ViewData["Title"] + + + +} + +
+

گالری نتایج قبل و بعد

+

نمونه‌ای از نتایج واقعی درمان‌های انجام‌شده توسط دکتر سوسن آل‌طه. روی هر تصویر، قبل و بعد درمان قابل مشاهده است.

+
+ +@if (Model.Categories.Any()) +{ +
+ + @foreach (var cat in Model.Categories) + { + + } +
+} + +
+ @if (!Model.Items.Any()) + { +

هنوز نمونه‌ای ثبت نشده است.

+ } + else + { + @foreach (var item in Model.Items) + { + var hasBoth = !string.IsNullOrEmpty(item.BeforeImageUrl) && !string.IsNullOrEmpty(item.AfterImageUrl); + var hasImg = !string.IsNullOrEmpty(item.ImageUrl); +
+
+ @if (hasBoth) + { +
+
قبل از درمان @item.Caption
+
+
بعد از درمان @item.Caption
+
+ } + else if (hasImg) + { + @item.Caption + } +
+ @if (hasBoth) + { +
قبل از درمانبعد از درمان
+ } + @if (!string.IsNullOrEmpty(item.Caption)) {
@item.Caption
} +
+ } + } +
+ + + +@section Scripts { + +} diff --git a/DrSousan.Api/Pages/Gallery.cshtml.cs b/DrSousan.Api/Pages/Gallery.cshtml.cs new file mode 100644 index 0000000..9444eb7 --- /dev/null +++ b/DrSousan.Api/Pages/Gallery.cshtml.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.EntityFrameworkCore; +using DrSousan.Api.Data; +using DrSousan.Api.Models; + +namespace DrSousan.Api.Pages; + +public class GalleryModel : PageModel +{ + private readonly AppDbContext _db; + + public GalleryModel(AppDbContext db) => _db = db; + + public List Items { get; private set; } = new(); + public List Categories { get; private set; } = new(); + + public async Task OnGetAsync() + { + Items = await _db.GalleryItems + .Where(g => g.IsActive) + .OrderBy(g => g.Order) + .ToListAsync(); + + Categories = Items + .Where(i => !string.IsNullOrWhiteSpace(i.Category)) + .Select(i => i.Category.Trim()) + .Distinct() + .ToList(); + + var siteName = (await _db.SiteSettings + .FirstOrDefaultAsync(x => x.Section == "hero" && x.Key == "name"))?.Value + ?? "دکتر سوسن آل‌طه"; + ViewData["SiteName"] = siteName; + ViewData["Title"] = $"گالری نتایج قبل و بعد | {siteName}"; + } +} diff --git a/DrSousan.Api/Pages/Index.cshtml b/DrSousan.Api/Pages/Index.cshtml index ad0ac6f..0156e3c 100644 --- a/DrSousan.Api/Pages/Index.cshtml +++ b/DrSousan.Api/Pages/Index.cshtml @@ -529,13 +529,6 @@

نمونه‌ای از نتایج فوق‌العاده درمان‌های انجام‌شده توسط دکتر آل‌طه.

- + @if (Model.GalleryTotal > 3) + { + + } diff --git a/DrSousan.Api/Pages/Index.cshtml.cs b/DrSousan.Api/Pages/Index.cshtml.cs index d599bce..9f2473e 100644 --- a/DrSousan.Api/Pages/Index.cshtml.cs +++ b/DrSousan.Api/Pages/Index.cshtml.cs @@ -19,6 +19,7 @@ public class IndexModel : PageModel // Collections public List Services { get; private set; } = new(); public List Gallery { get; private set; } = new(); + public int GalleryTotal { get; private set; } = 0; public List Testimonials { get; private set; } = new(); public List RecentPosts { get; private set; } = new(); public List Faqs { get; private set; } = new(); @@ -39,9 +40,12 @@ public class IndexModel : PageModel .OrderBy(s => s.Order) .ToListAsync(); - Gallery = await _db.GalleryItems - .Where(g => g.IsActive) + // Homepage shows only a teaser of 3; full set lives on /gallery + var galleryQuery = _db.GalleryItems.Where(g => g.IsActive); + GalleryTotal = await galleryQuery.CountAsync(); + Gallery = await galleryQuery .OrderBy(g => g.Order) + .Take(3) .ToListAsync(); Testimonials = await _db.Testimonials diff --git a/DrSousan.Api/Program.cs b/DrSousan.Api/Program.cs index 5103b69..61d79c7 100644 --- a/DrSousan.Api/Program.cs +++ b/DrSousan.Api/Program.cs @@ -803,6 +803,7 @@ app.MapGet("/sitemap.xml", async (AppDbContext db, HttpContext ctx) => } Url(baseUrl + "/", "1.0", "weekly", DateTime.UtcNow); + Url(baseUrl + "/gallery", "0.8", "weekly", DateTime.UtcNow); Url(baseUrl + "/blog", "0.9", "daily", DateTime.UtcNow); foreach (var p in published) Url($"{baseUrl}/blog/{p.Slug}", "0.8", "monthly", p.UpdatedAt); diff --git a/DrSousan.Api/wwwroot/admin/index.html b/DrSousan.Api/wwwroot/admin/index.html index 22c8e23..1b765ae 100644 --- a/DrSousan.Api/wwwroot/admin/index.html +++ b/DrSousan.Api/wwwroot/admin/index.html @@ -1027,6 +1027,7 @@ tr:hover td{background:#FAFBFC} +
@@ -2244,6 +2245,31 @@ function checkSeo(){ function fmt(cmd){document.getElementById('post-content').focus();document.execCommand(cmd);} function fmtBlock(tag){document.getElementById('post-content').focus();document.execCommand('formatBlock',false,tag);} function insLink(){const url=prompt('آدرس لینک:');if(url){document.getElementById('post-content').focus();document.execCommand('createLink',false,url);}} +// Insert an image into the post content at the cursor (uploads directly, no forced crop) +function insImage(){ + const editor=document.getElementById('post-content'); + editor.focus(); + const sel=window.getSelection(); + const savedRange=sel.rangeCount?sel.getRangeAt(0):null; + const fileInput=document.createElement('input'); + fileInput.type='file'; + fileInput.accept='image/jpeg,image/png,image/webp,image/gif'; + fileInput.onchange=async()=>{ + const file=fileInput.files[0];if(!file)return; + toast('در حال آپلود تصویر...'); + try{ + const fd=new FormData();fd.append('file',file); + const r=await fetch('/api/upload',{method:'POST',headers:{'Authorization':`Bearer ${token}`},body:fd}); + if(!r.ok)throw new Error(await r.text()); + const {url}=await r.json(); + editor.focus(); + if(savedRange){sel.removeAllRanges();sel.addRange(savedRange);} + document.execCommand('insertHTML',false,`


`); + toast('تصویر در متن درج شد ✓'); + }catch(e){toast('خطا در آپلود: '+e.message,'error');} + }; + fileInput.click(); +} // ── Modal helpers ───────────────────────────────────────────────────────────── function closeModal(id){document.getElementById(id).classList.add('hidden');}