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>();
if (!string.IsNullOrEmpty(model.WhatsAppNumber))
{
var whatsappDigits = model.WhatsAppNumber
.Replace("https://wa.me/", "").Replace("whatsapp://", "")
.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "");
socialLinks.Add(new LinkItem
{
Title = "WhatsApp",
Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}",
Url = $"https://wa.me/{whatsappDigits}",
Icon = "fab fa-whatsapp",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
@ -837,6 +840,16 @@ public class AdminController : Controller
FileSize = d.FileSize,
UploadedAt = d.UploadedAt
}).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,
AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(),
MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(),
@ -892,10 +905,13 @@ public class AdminController : Controller
if (!string.IsNullOrEmpty(model.WhatsAppNumber))
{
var whatsappDigits = model.WhatsAppNumber
.Replace("https://wa.me/", "").Replace("whatsapp://", "")
.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "");
socialLinks.Add(new LinkItem
{
Title = "WhatsApp",
Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}",
Url = $"https://wa.me/{whatsappDigits}",
Icon = "fab fa-whatsapp",
IsActive = true,
Order = currentOrder++
@ -1241,10 +1257,13 @@ public class AdminController : Controller
if (!string.IsNullOrEmpty(model.WhatsAppNumber))
{
var whatsappDigits = model.WhatsAppNumber
.Replace("https://wa.me/", "").Replace("whatsapp://", "")
.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "");
socialLinks.Add(new LinkItem
{
Title = "WhatsApp",
Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}",
Url = $"https://wa.me/{whatsappDigits}",
Icon = "fab fa-whatsapp",
IsActive = true,
Order = currentOrder++

View File

@ -82,9 +82,15 @@
<div class="mb-3">
<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>
<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>
@ -616,4 +622,51 @@ function generatePreview() {
@section Scripts {
@{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">
<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>
<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>
<!-- Profile Image Upload -->
@ -1210,7 +1216,6 @@
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script>
const LINK_TYPES_CONFIG = @Html.Raw(linkTypesJson);
let linkCount = @Model.Links.Count;
@ -1219,6 +1224,9 @@
const totalSteps = 5;
$(document).ready(function() {
// Initialize Markdown toolbar
initMarkdownToolbar();
// Initialize social media fields
initializeSocialMedia();
@ -1337,11 +1345,11 @@
updateLinkNumbers();
});
// Form validation
// Loading state — só desabilita se a validação JS não bloqueou o submit
$('#managePageForm').on('submit', function(e) {
console.log('Form submitted');
// 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>Criando...');
if (!e.isDefaultPrevented()) {
$(this).find('button[type="submit"]').prop('disabled', true).html('<i class="fas fa-spinner fa-spin me-2"></i>Salvando...');
}
});
});
@ -1943,6 +1951,53 @@
}, 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
function checkValidationErrors() {
// 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
// WhatsApp: armazena só o número (servidor adiciona https://wa.me/ ao salvar)
// Outros: armazena URL completa (servidor usa diretamente)
if (value) {
hiddenField.val(prefix + value);
hiddenField.val(isWhatsApp ? value : prefix + value);
} else {
hiddenField.val(' '); // Espaço em branco para evitar null
}

View File

@ -103,9 +103,11 @@
<div class="card-body">
@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">
<strong>Biografia:</strong>
<p>@Model.Page.Bio</p>
<div>@Html.Raw(bioHtml)</div>
</div>
}

View File

@ -367,7 +367,9 @@
@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 -->

View File

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