forked from UKSOURCE/cms.hailearning.edu.vn
refactor(about): remove blog section and decouple from blog module
This commit is contained in:
@@ -281,36 +281,65 @@
|
||||
<!-- News Tab -->
|
||||
<div class="tab-pane fade <%= locals.activeTab === 'news' ? 'show active' : '' %>" id="news" role="tabpanel">
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0"><i class="fas fa-newspaper me-2"></i>News Section</h6>
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0"><i class="fas fa-newspaper me-2"></i>News Section (Blog Preview)</h6>
|
||||
<span class="badge bg-info text-dark">System will automatically fetch the 3 latest posts if no specific blog is selected.</span>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Subheading</label>
|
||||
<input type="text" class="form-control" id="newsSubheading" value="<%= data.news?.subheading || '' %>">
|
||||
<label class="form-label fw-medium">Subheading</label>
|
||||
<input type="text" class="form-control" id="newsSubheading" value="<%= data.news?.subheading || '' %>" placeholder="e.g., Visa Tips & Guides">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Heading</label>
|
||||
<input type="text" class="form-control" id="newsHeading" value="<%= data.news?.heading || '' %>">
|
||||
<label class="form-label fw-medium">Heading</label>
|
||||
<input type="text" class="form-control" id="newsHeading" value="<%= data.news?.heading || '' %>" placeholder="e.g., Latest Insights & Updates">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">CTA Button Label</label>
|
||||
<input type="text" class="form-control" id="newsCtaLabel" value="<%= data.news?.ctaButton?.label || '' %>">
|
||||
<label class="form-label fw-medium">CTA Button Label</label>
|
||||
<input type="text" class="form-control" id="newsCtaLabel" value="<%= data.news?.ctaButton?.label || '' %>" placeholder="e.g., View All Articles">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">CTA Button Link</label>
|
||||
<input type="text" class="form-control" id="newsCtaHref" value="<%= data.news?.ctaButton?.href || '' %>">
|
||||
<label class="form-label fw-medium">CTA Button Link</label>
|
||||
<input type="text" class="form-control" id="newsCtaHref" value="<%= data.news?.ctaButton?.href || '' %>" placeholder="/blog">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6 class="mb-0">News Articles</h6>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="addNewsItem()">
|
||||
<i class="fas fa-plus me-1"></i>Add Article
|
||||
</button>
|
||||
<div class="col-md-12 mt-4">
|
||||
<label class="form-label fw-bold"><i class="fas fa-check-square me-2"></i>Select Featured Blogs (Direct from Blog Module)</label>
|
||||
<p class="text-muted small mb-3">Select blog posts to display on About page. If none are selected, the system will use the 3 latest posts.</p>
|
||||
<div class="row g-3 blog-selector-container" style="max-height: 400px; overflow-y: auto; border: 1px solid #eee; padding: 15px; border-radius: 8px;">
|
||||
<% if (allBlogs && allBlogs.length > 0) { %>
|
||||
<% allBlogs.forEach(blog => {
|
||||
const isSelected = data.news?.selectedBlogIds && data.news.selectedBlogIds.some(id => id.toString() === blog._id.toString());
|
||||
%>
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100 blog-select-card <%= isSelected ? 'border-primary bg-light' : '' %>" onclick="toggleAboutBlogSelection(this, '<%= blog._id %>')" style="cursor: pointer; transition: all 0.2s;">
|
||||
<div class="position-absolute top-0 end-0 m-2">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input about-blog-checkbox" type="checkbox" value="<%= blog._id %>" <%= isSelected ? 'checked' : '' %> onclick="event.stopPropagation(); handleAboutCheckboxChange(this)">
|
||||
</div>
|
||||
</div>
|
||||
<img src="<%= blog.featuredImage ? (blog.featuredImage.startsWith('http') ? blog.featuredImage : backendUrl + blog.featuredImage) : '/assets/img/placeholder.jpg' %>" class="card-img-top" style="height: 210px; object-fit: cover;">
|
||||
<div class="card-body p-2">
|
||||
<h6 class="card-title small fw-bold mb-1" title="<%= blog.title %>" style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; height: 2.6em; line-height: 1.3em;">
|
||||
<%= blog.title %>
|
||||
</h6>
|
||||
<p class="card-text tiny text-muted mb-0">
|
||||
<%= blog.publishedAt ? new Date(blog.publishedAt).toLocaleDateString('vi-VN') : '' %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }) %>
|
||||
<% } else { %>
|
||||
<div class="col-12 text-center py-4">
|
||||
<p class="text-muted">No published blogs found. Please create some blogs first.</p>
|
||||
<a href="/admin/blog/create" class="btn btn-sm btn-outline-primary">Create Blog</a>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<div id="newsItemsContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -638,7 +667,16 @@
|
||||
document.getElementById('newsHeading').value = news.heading || '';
|
||||
document.getElementById('newsCtaLabel').value = news.ctaButton?.label || '';
|
||||
document.getElementById('newsCtaHref').value = news.ctaButton?.href || '';
|
||||
populateNewsItems(news.items || []);
|
||||
|
||||
// Update blog selection checkboxes
|
||||
document.querySelectorAll('.about-blog-checkbox').forEach(cb => {
|
||||
const isSelected = news.selectedBlogIds && news.selectedBlogIds.some(id => id.toString() === cb.value);
|
||||
cb.checked = isSelected;
|
||||
const card = cb.closest('.blog-select-card');
|
||||
if (card) {
|
||||
handleAboutCheckboxUpdate(card, isSelected);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateImagePreview(inputId, imagePath) {
|
||||
@@ -760,77 +798,42 @@
|
||||
});
|
||||
}
|
||||
|
||||
function addNewsItem() {
|
||||
const container = document.getElementById('newsItemsContainer');
|
||||
const idx = container.children.length;
|
||||
const html = `
|
||||
<div class="card mb-3 news-item-row">
|
||||
<div class="card-body p-3">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<label class="small text-muted">Title</label>
|
||||
<input type="text" class="form-control form-control-sm" name="newsItemTitle_${idx}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="small text-muted">Category</label>
|
||||
<input type="text" class="form-control form-control-sm" name="newsItemCategory_${idx}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="small text-muted">Date</label>
|
||||
<input type="text" class="form-control form-control-sm" name="newsItemDate_${idx}">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="small text-muted">Comments</label>
|
||||
<input type="number" class="form-control form-control-sm" name="newsItemComments_${idx}">
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="small text-muted">Author Name</label>
|
||||
<input type="text" class="form-control form-control-sm" name="newsItemAuthorName_${idx}">
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="small text-muted">Author Avatar</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" class="form-control" name="newsItemAuthorAvatar_${idx}">
|
||||
<button class="btn btn-outline-primary btn-upload-image" type="button" data-target-input="newsItemAuthorAvatar_${idx}" data-image-type="about">
|
||||
<i class="fas fa-upload"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="small text-muted">Link</label>
|
||||
<input type="text" class="form-control form-control-sm" name="newsItemLink_${idx}">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="small text-muted">Thumbnail</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" class="form-control" name="newsItemThumbnail_${idx}">
|
||||
<button class="btn btn-outline-primary btn-upload-image" type="button" data-target-input="newsItemThumbnail_${idx}" data-image-type="about">
|
||||
<i class="fas fa-upload"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm mt-2" onclick="this.closest('.news-item-row').remove()">Remove Article</button>
|
||||
</div>
|
||||
</div>`;
|
||||
container.insertAdjacentHTML('beforeend', html);
|
||||
// Blog selection functions for About News section
|
||||
function toggleAboutBlogSelection(card, blogId) {
|
||||
const checkbox = card.querySelector('.about-blog-checkbox');
|
||||
const isChecking = !checkbox.checked;
|
||||
|
||||
if (isChecking) {
|
||||
const checkedCount = document.querySelectorAll('.about-blog-checkbox:checked').length;
|
||||
if (checkedCount >= 3) {
|
||||
alert('You can only select up to 3 blogs.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
checkbox.checked = isChecking;
|
||||
handleAboutCheckboxUpdate(card, checkbox.checked);
|
||||
}
|
||||
|
||||
function populateNewsItems(items) {
|
||||
const container = document.getElementById('newsItemsContainer');
|
||||
container.innerHTML = '';
|
||||
items.forEach((item, i) => {
|
||||
addNewsItem();
|
||||
const last = container.lastElementChild;
|
||||
last.querySelector(`[name="newsItemTitle_${i}"]`).value = item.title || '';
|
||||
last.querySelector(`[name="newsItemCategory_${i}"]`).value = item.category || '';
|
||||
last.querySelector(`[name="newsItemDate_${i}"]`).value = item.date || '';
|
||||
last.querySelector(`[name="newsItemComments_${i}"]`).value = item.comments || 0;
|
||||
last.querySelector(`[name="newsItemAuthorName_${i}"]`).value = item.author?.name || '';
|
||||
last.querySelector(`[name="newsItemAuthorAvatar_${i}"]`).value = item.author?.avatar || '';
|
||||
last.querySelector(`[name="newsItemLink_${i}"]`).value = item.link || '';
|
||||
last.querySelector(`[name="newsItemThumbnail_${i}"]`).value = item.thumbnail || '';
|
||||
});
|
||||
function handleAboutCheckboxChange(checkbox) {
|
||||
if (checkbox.checked) {
|
||||
const checkedCount = document.querySelectorAll('.about-blog-checkbox:checked').length;
|
||||
if (checkedCount > 3) {
|
||||
checkbox.checked = false;
|
||||
alert('You can only select up to 3 blogs.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
const card = checkbox.closest('.blog-select-card');
|
||||
handleAboutCheckboxUpdate(card, checkbox.checked);
|
||||
}
|
||||
|
||||
function handleAboutCheckboxUpdate(card, isChecked) {
|
||||
if (isChecked) {
|
||||
card.classList.add('border-primary', 'bg-light');
|
||||
} else {
|
||||
card.classList.remove('border-primary', 'bg-light');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -926,6 +929,11 @@
|
||||
document.getElementById('featuresJson').value = JSON.stringify(featuresData);
|
||||
|
||||
// News
|
||||
const selectedIds = [];
|
||||
document.querySelectorAll('.about-blog-checkbox:checked').forEach(cb => {
|
||||
selectedIds.push(cb.value);
|
||||
});
|
||||
|
||||
const newsData = {
|
||||
subheading: document.getElementById('newsSubheading').value.trim(),
|
||||
heading: document.getElementById('newsHeading').value.trim(),
|
||||
@@ -933,18 +941,8 @@
|
||||
label: document.getElementById('newsCtaLabel').value.trim(),
|
||||
href: document.getElementById('newsCtaHref').value.trim()
|
||||
},
|
||||
items: Array.from(document.querySelectorAll('.news-item-row')).map(item => ({
|
||||
title: item.querySelector('[name^="newsItemTitle_"]').value.trim(),
|
||||
category: item.querySelector('[name^="newsItemCategory_"]').value.trim(),
|
||||
date: item.querySelector('[name^="newsItemDate_"]').value.trim(),
|
||||
comments: parseInt(item.querySelector('[name^="newsItemComments_"]').value) || 0,
|
||||
author: {
|
||||
name: item.querySelector('[name^="newsItemAuthorName_"]').value.trim(),
|
||||
avatar: item.querySelector('[name^="newsItemAuthorAvatar_"]').value.trim()
|
||||
},
|
||||
link: item.querySelector('[name^="newsItemLink_"]').value.trim(),
|
||||
thumbnail: item.querySelector('[name^="newsItemThumbnail_"]').value.trim()
|
||||
})).filter(i => i.title !== '')
|
||||
selectedBlogIds: selectedIds,
|
||||
items: [] // Server will populate this from selectedBlogIds
|
||||
};
|
||||
document.getElementById('newsJson').value = JSON.stringify(newsData);
|
||||
|
||||
@@ -955,4 +953,15 @@
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tiny {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.blog-select-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user