fix: ajustes na edição

This commit is contained in:
Ricardo Carneiro 2026-04-30 23:24:17 -03:00
parent dca698e4c4
commit cff7962cc5
6 changed files with 149 additions and 15 deletions

View File

@ -558,10 +558,13 @@ public class AdminController : Controller
var socialLinks = new List<LinkItem>(); var socialLinks = new List<LinkItem>();
if (!string.IsNullOrEmpty(model.WhatsAppNumber)) if (!string.IsNullOrEmpty(model.WhatsAppNumber))
{ {
var whatsappDigits = model.WhatsAppNumber
.Replace("https://wa.me/", "").Replace("whatsapp://", "")
.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "");
socialLinks.Add(new LinkItem socialLinks.Add(new LinkItem
{ {
Title = "WhatsApp", Title = "WhatsApp",
Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}", Url = $"https://wa.me/{whatsappDigits}",
Icon = "fab fa-whatsapp", Icon = "fab fa-whatsapp",
IsActive = true, IsActive = true,
Order = userPage.Links.Count + socialLinks.Count Order = userPage.Links.Count + socialLinks.Count
@ -837,6 +840,16 @@ public class AdminController : Controller
FileSize = d.FileSize, FileSize = d.FileSize,
UploadedAt = d.UploadedAt UploadedAt = d.UploadedAt
}).ToList() ?? new List<ManageDocumentViewModel>(), }).ToList() ?? new List<ManageDocumentViewModel>(),
// Social media fields — extracted from Links so the edit form pre-fills correctly
WhatsAppNumber = page.Links?.FirstOrDefault(l => l.Icon?.Contains("whatsapp") == true)?.Url
?.Replace("https://wa.me/", "").Replace("whatsapp://", "") ?? string.Empty,
FacebookUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("facebook") == true)?.Url ?? string.Empty,
InstagramUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("instagram") == true)?.Url ?? string.Empty,
TwitterUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("twitter") == true)?.Url ?? string.Empty,
TiktokUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("tiktok") == true)?.Url ?? string.Empty,
PinterestUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("pinterest") == true)?.Url ?? string.Empty,
DiscordUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("discord") == true)?.Url ?? string.Empty,
KawaiUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("kawai") == true)?.Url ?? string.Empty,
AvailableCategories = categories, AvailableCategories = categories,
AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(), AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(),
MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(), MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(),
@ -892,10 +905,13 @@ public class AdminController : Controller
if (!string.IsNullOrEmpty(model.WhatsAppNumber)) if (!string.IsNullOrEmpty(model.WhatsAppNumber))
{ {
var whatsappDigits = model.WhatsAppNumber
.Replace("https://wa.me/", "").Replace("whatsapp://", "")
.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "");
socialLinks.Add(new LinkItem socialLinks.Add(new LinkItem
{ {
Title = "WhatsApp", Title = "WhatsApp",
Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}", Url = $"https://wa.me/{whatsappDigits}",
Icon = "fab fa-whatsapp", Icon = "fab fa-whatsapp",
IsActive = true, IsActive = true,
Order = currentOrder++ Order = currentOrder++
@ -1241,10 +1257,13 @@ public class AdminController : Controller
if (!string.IsNullOrEmpty(model.WhatsAppNumber)) if (!string.IsNullOrEmpty(model.WhatsAppNumber))
{ {
var whatsappDigits = model.WhatsAppNumber
.Replace("https://wa.me/", "").Replace("whatsapp://", "")
.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "");
socialLinks.Add(new LinkItem socialLinks.Add(new LinkItem
{ {
Title = "WhatsApp", Title = "WhatsApp",
Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}", Url = $"https://wa.me/{whatsappDigits}",
Icon = "fab fa-whatsapp", Icon = "fab fa-whatsapp",
IsActive = true, IsActive = true,
Order = currentOrder++ Order = currentOrder++

View File

@ -82,9 +82,15 @@
<div class="mb-3"> <div class="mb-3">
<label asp-for="Bio" class="form-label">Bio/Descrição</label> <label asp-for="Bio" class="form-label">Bio/Descrição</label>
<textarea asp-for="Bio" class="form-control" rows="8" maxlength="3000" placeholder="Uma breve descrição sobre você ou sua empresa..."></textarea> <div class="md-toolbar border rounded-top border-bottom-0 bg-light px-2 py-1 d-flex gap-1">
<button type="button" class="btn btn-sm btn-outline-secondary md-btn" data-target="Bio" data-wrap="**" title="Negrito"><b>B</b></button>
<button type="button" class="btn btn-sm btn-outline-secondary md-btn" data-target="Bio" data-wrap="*" title="Itálico"><i>I</i></button>
<button type="button" class="btn btn-sm btn-outline-secondary md-list-btn" data-target="Bio" title="Lista">&#8226; Lista</button>
<button type="button" class="btn btn-sm btn-outline-secondary md-link-btn" data-target="Bio" title="Link">&#128279; Link</button>
</div>
<textarea asp-for="Bio" id="Bio" class="form-control rounded-0 rounded-bottom" rows="5" maxlength="3000" placeholder="Uma breve descrição sobre você ou sua empresa..." style="font-family: monospace; font-size: 0.9rem;"></textarea>
<span asp-validation-for="Bio" class="text-danger"></span> <span asp-validation-for="Bio" class="text-danger"></span>
<div class="form-text">Máximo 3000 caracteres</div> <div class="form-text">Máximo 3000 caracteres. Use **negrito**, *itálico*, - item para listas.</div>
</div> </div>
</div> </div>
@ -616,4 +622,51 @@ function generatePreview() {
@section Scripts { @section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");} @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script>
$(document).ready(function() {
document.querySelectorAll('.md-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var targetId = this.dataset.target;
var wrap = this.dataset.wrap;
var ta = document.getElementById(targetId);
var start = ta.selectionStart, end = ta.selectionEnd;
var sel = ta.value.substring(start, end) || 'texto';
var before = ta.value.substring(0, start);
var after = ta.value.substring(end);
ta.value = before + wrap + sel + wrap + after;
ta.selectionStart = start + wrap.length;
ta.selectionEnd = start + wrap.length + sel.length;
ta.focus();
});
});
document.querySelectorAll('.md-list-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var targetId = this.dataset.target;
var ta = document.getElementById(targetId);
var start = ta.selectionStart;
var lineStart = ta.value.lastIndexOf('\n', start - 1) + 1;
var before = ta.value.substring(0, lineStart);
var after = ta.value.substring(lineStart);
ta.value = before + '- ' + after;
ta.selectionStart = ta.selectionEnd = start + 2;
ta.focus();
});
});
document.querySelectorAll('.md-link-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var targetId = this.dataset.target;
var ta = document.getElementById(targetId);
var start = ta.selectionStart, end = ta.selectionEnd;
var sel = ta.value.substring(start, end) || 'texto do link';
var url = prompt('URL do link:') || 'https://';
var before = ta.value.substring(0, start);
var after = ta.value.substring(end);
var md = '[' + sel + '](' + url + ')';
ta.value = before + md + after;
ta.selectionStart = ta.selectionEnd = start + md.length;
ta.focus();
});
});
});
</script>
} }

View File

@ -115,9 +115,15 @@
<div class="mb-3"> <div class="mb-3">
<label asp-for="Bio" class="form-label">Bio/Descrição</label> <label asp-for="Bio" class="form-label">Bio/Descrição</label>
<textarea asp-for="Bio" class="form-control" rows="8" maxlength="3000" placeholder="Uma breve descrição sobre você ou sua empresa..."></textarea> <div class="md-toolbar border rounded-top border-bottom-0 bg-light px-2 py-1 d-flex gap-1">
<button type="button" class="btn btn-sm btn-outline-secondary md-btn" data-target="Bio" data-wrap="**" title="Negrito"><b>B</b></button>
<button type="button" class="btn btn-sm btn-outline-secondary md-btn" data-target="Bio" data-wrap="*" title="Itálico"><i>I</i></button>
<button type="button" class="btn btn-sm btn-outline-secondary md-btn md-list-btn" data-target="Bio" title="Lista">&#8226; Lista</button>
<button type="button" class="btn btn-sm btn-outline-secondary md-link-btn" data-target="Bio" title="Link">&#128279; Link</button>
</div>
<textarea asp-for="Bio" id="Bio" class="form-control rounded-0 rounded-bottom" rows="5" maxlength="3000" placeholder="Uma breve descrição sobre você ou sua empresa..." style="font-family: monospace; font-size: 0.9rem;"></textarea>
<span asp-validation-for="Bio" class="text-danger"></span> <span asp-validation-for="Bio" class="text-danger"></span>
<div class="form-text">Máximo 3000 caracteres</div> <div class="form-text">Máximo 3000 caracteres. Use **negrito**, *itálico*, - item para listas.</div>
</div> </div>
<!-- Profile Image Upload --> <!-- Profile Image Upload -->
@ -1210,7 +1216,6 @@
@section Scripts { @section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");} @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script> <script>
const LINK_TYPES_CONFIG = @Html.Raw(linkTypesJson); const LINK_TYPES_CONFIG = @Html.Raw(linkTypesJson);
let linkCount = @Model.Links.Count; let linkCount = @Model.Links.Count;
@ -1219,6 +1224,9 @@
const totalSteps = 5; const totalSteps = 5;
$(document).ready(function() { $(document).ready(function() {
// Initialize Markdown toolbar
initMarkdownToolbar();
// Initialize social media fields // Initialize social media fields
initializeSocialMedia(); initializeSocialMedia();
@ -1337,11 +1345,11 @@
updateLinkNumbers(); updateLinkNumbers();
}); });
// Form validation // Loading state — só desabilita se a validação JS não bloqueou o submit
$('#managePageForm').on('submit', function(e) { $('#managePageForm').on('submit', function(e) {
console.log('Form submitted'); if (!e.isDefaultPrevented()) {
// Allow submission but add loading state $(this).find('button[type="submit"]').prop('disabled', true).html('<i class="fas fa-spinner fa-spin me-2"></i>Salvando...');
$(this).find('button[type="submit"]').prop('disabled', true).html('<i class="fas fa-spinner fa-spin me-2"></i>Criando...'); }
}); });
}); });
@ -1943,6 +1951,53 @@
}, 7000); }, 7000);
} }
// Markdown Toolbar
function initMarkdownToolbar() {
document.querySelectorAll('.md-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var targetId = this.dataset.target;
var wrap = this.dataset.wrap;
var ta = document.getElementById(targetId);
var start = ta.selectionStart, end = ta.selectionEnd;
var sel = ta.value.substring(start, end) || 'texto';
var before = ta.value.substring(0, start);
var after = ta.value.substring(end);
ta.value = before + wrap + sel + wrap + after;
ta.selectionStart = start + wrap.length;
ta.selectionEnd = start + wrap.length + sel.length;
ta.focus();
});
});
document.querySelectorAll('.md-list-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var targetId = this.dataset.target;
var ta = document.getElementById(targetId);
var start = ta.selectionStart;
var lineStart = ta.value.lastIndexOf('\n', start - 1) + 1;
var before = ta.value.substring(0, lineStart);
var after = ta.value.substring(lineStart);
ta.value = before + '- ' + after;
ta.selectionStart = ta.selectionEnd = start + 2;
ta.focus();
});
});
document.querySelectorAll('.md-link-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var targetId = this.dataset.target;
var ta = document.getElementById(targetId);
var start = ta.selectionStart, end = ta.selectionEnd;
var sel = ta.value.substring(start, end) || 'texto do link';
var url = prompt('URL do link:') || 'https://';
var before = ta.value.substring(0, start);
var after = ta.value.substring(end);
var md = '[' + sel + '](' + url + ')';
ta.value = before + md + after;
ta.selectionStart = ta.selectionEnd = start + md.length;
ta.focus();
});
});
}
// Validation Error Handling // Validation Error Handling
function checkValidationErrors() { function checkValidationErrors() {
// Só verificar erros se estamos em um POST-back (ou seja, se ModelState foi validado) // Só verificar erros se estamos em um POST-back (ou seja, se ModelState foi validado)
@ -2262,8 +2317,10 @@
} }
// Atualizar campo hidden - SEMPRE string, nunca null // Atualizar campo hidden - SEMPRE string, nunca null
// WhatsApp: armazena só o número (servidor adiciona https://wa.me/ ao salvar)
// Outros: armazena URL completa (servidor usa diretamente)
if (value) { if (value) {
hiddenField.val(prefix + value); hiddenField.val(isWhatsApp ? value : prefix + value);
} else { } else {
hiddenField.val(' '); // Espaço em branco para evitar null hiddenField.val(' '); // Espaço em branco para evitar null
} }

View File

@ -103,9 +103,11 @@
<div class="card-body"> <div class="card-body">
@if (!string.IsNullOrEmpty(Model.Page.Bio)) @if (!string.IsNullOrEmpty(Model.Page.Bio))
{ {
var bioPipeline = new Markdig.MarkdownPipelineBuilder().UseAutoLinks().DisableHtml().Build();
var bioHtml = Markdig.Markdown.ToHtml(Model.Page.Bio, bioPipeline);
<div class="mb-3"> <div class="mb-3">
<strong>Biografia:</strong> <strong>Biografia:</strong>
<p>@Model.Page.Bio</p> <div>@Html.Raw(bioHtml)</div>
</div> </div>
} }

View File

@ -367,7 +367,9 @@
@if (!string.IsNullOrEmpty(Model.Bio)) @if (!string.IsNullOrEmpty(Model.Bio))
{ {
<p class="profile-bio">@Model.Bio</p> var bioPipeline = new Markdig.MarkdownPipelineBuilder().UseAutoLinks().DisableHtml().Build();
var bioHtml = Markdig.Markdown.ToHtml(Model.Bio, bioPipeline);
<div class="profile-bio">@Html.Raw(bioHtml)</div>
} }
<!-- Links Container --> <!-- Links Container -->

View File

@ -1,3 +1,4 @@
@using BCards.Web @using BCards.Web
@using BCards.Web.Models @using BCards.Web.Models
@using Markdig
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers