175 lines
5.7 KiB
JavaScript
175 lines
5.7 KiB
JavaScript
/* Minimal blog-posts.js
|
|
- Stores posts in localStorage under `blogPosts`
|
|
- Posts: {id, title, content, image, journeyId, created_at}
|
|
- Supports create, edit, delete, view
|
|
*/
|
|
|
|
const STORAGE_KEY = 'blogPosts';
|
|
|
|
function loadPosts() {
|
|
try {
|
|
return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
|
|
} catch (e) {
|
|
console.error('Failed to parse posts', e);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function savePosts(posts) {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(posts));
|
|
}
|
|
|
|
function getNextId(posts) {
|
|
if (!posts || posts.length === 0) return 1;
|
|
return Math.max(...posts.map(p => p.id)) + 1;
|
|
}
|
|
|
|
function renderPostList() {
|
|
const posts = loadPosts().sort((a,b)=> new Date(b.created_at) - new Date(a.created_at));
|
|
const container = document.getElementById('posts-list');
|
|
container.innerHTML = '';
|
|
|
|
if (posts.length === 0) {
|
|
container.innerHTML = '<p class="empty-message">No posts yet.</p>';
|
|
return;
|
|
}
|
|
|
|
posts.forEach(post => {
|
|
const el = document.createElement('div');
|
|
el.className = 'marker-item';
|
|
el.innerHTML = `
|
|
<div style="display:flex;justify-content:space-between;align-items:center;gap:8px;">
|
|
<div style="flex:1">
|
|
<strong>${escapeHtml(post.title)}</strong>
|
|
<div style="font-size:.85rem;color:#7f8c8d">${new Date(post.created_at).toLocaleString()}${post.journeyId ? ' • Journey '+post.journeyId : ''}</div>
|
|
</div>
|
|
<div style="display:flex;gap:6px">
|
|
<button class="btn btn-small" data-action="view" data-id="${post.id}">View</button>
|
|
<button class="btn btn-small" data-action="edit" data-id="${post.id}">Edit</button>
|
|
<button class="btn btn-small btn-danger" data-action="delete" data-id="${post.id}">Delete</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
container.appendChild(el);
|
|
});
|
|
}
|
|
|
|
function escapeHtml(s){
|
|
return String(s || '').replace(/[&<>"']/g, (m)=>({
|
|
'&':'&','<':'<','>':'>','"':'"',"'":'''
|
|
}[m]));
|
|
}
|
|
|
|
function showPost(id) {
|
|
const posts = loadPosts();
|
|
const post = posts.find(p=>p.id===id);
|
|
if (!post) return;
|
|
|
|
const titleEl = document.querySelector('.post-title');
|
|
const metaEl = document.querySelector('.post-meta');
|
|
const imgEl = document.querySelector('.post-hero img');
|
|
const contentEl = document.querySelector('.post-content');
|
|
|
|
titleEl.textContent = post.title;
|
|
metaEl.innerHTML = `By <span class="author">Author</span> — <time datetime="${post.created_at}">${new Date(post.created_at).toLocaleString()}</time>`;
|
|
if (post.image) imgEl.src = post.image; else imgEl.src = 'https://via.placeholder.com/800x350?text=Hero+Image';
|
|
contentEl.innerHTML = post.content;
|
|
}
|
|
|
|
function openForm(mode='create', post=null) {
|
|
const formWrap = document.getElementById('post-form');
|
|
formWrap.style.display = 'block';
|
|
document.getElementById('form-title').textContent = mode === 'create' ? 'New Post' : 'Edit Post';
|
|
const form = document.getElementById('blog-form');
|
|
form.dataset.mode = mode;
|
|
form.dataset.id = post ? post.id : '';
|
|
document.getElementById('post-title-input').value = post ? post.title : '';
|
|
document.getElementById('post-journey-input').value = post ? (post.journeyId || '') : '';
|
|
document.getElementById('post-image-input').value = post ? (post.image || '') : '';
|
|
document.getElementById('post-content-input').value = post ? post.content : '';
|
|
}
|
|
|
|
function closeForm() {
|
|
const formWrap = document.getElementById('post-form');
|
|
formWrap.style.display = 'none';
|
|
const form = document.getElementById('blog-form');
|
|
form.removeAttribute('data-id');
|
|
}
|
|
|
|
function deletePost(id) {
|
|
if (!confirm('Delete this post?')) return;
|
|
let posts = loadPosts();
|
|
posts = posts.filter(p=>p.id!==id);
|
|
savePosts(posts);
|
|
renderPostList();
|
|
// if the shown post was deleted, clear article
|
|
const currentTitle = document.querySelector('.post-title').textContent;
|
|
if (!posts.find(p=>p.title===currentTitle)) {
|
|
document.querySelector('.post-title').textContent = 'Post Title';
|
|
document.querySelector('.post-content').innerHTML = '<p>No post selected.</p>';
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', ()=>{
|
|
renderPostList();
|
|
|
|
document.getElementById('create-post-btn').addEventListener('click', ()=> openForm('create'));
|
|
|
|
document.getElementById('cancel-post-btn').addEventListener('click', (e)=>{ e.preventDefault(); closeForm(); });
|
|
|
|
document.getElementById('posts-list').addEventListener('click', (e)=>{
|
|
const btn = e.target.closest('button[data-action]');
|
|
if (!btn) return;
|
|
const id = parseInt(btn.dataset.id,10);
|
|
const action = btn.dataset.action;
|
|
if (action === 'view') showPost(id);
|
|
if (action === 'edit') {
|
|
const posts = loadPosts();
|
|
const post = posts.find(p=>p.id===id);
|
|
openForm('edit', post);
|
|
}
|
|
if (action === 'delete') deletePost(id);
|
|
});
|
|
|
|
document.getElementById('blog-form').addEventListener('submit', (e)=>{
|
|
e.preventDefault();
|
|
const form = e.target;
|
|
const mode = form.dataset.mode || 'create';
|
|
const id = parseInt(form.dataset.id,10) || null;
|
|
const title = document.getElementById('post-title-input').value.trim();
|
|
const journeyId = document.getElementById('post-journey-input').value.trim() || null;
|
|
const image = document.getElementById('post-image-input').value.trim() || '';
|
|
const content = document.getElementById('post-content-input').value.trim();
|
|
|
|
let posts = loadPosts();
|
|
if (mode === 'create') {
|
|
const newPost = {
|
|
id: getNextId(posts),
|
|
title,
|
|
content,
|
|
image,
|
|
journeyId: journeyId || null,
|
|
created_at: new Date().toISOString()
|
|
};
|
|
posts.push(newPost);
|
|
savePosts(posts);
|
|
renderPostList();
|
|
showPost(newPost.id);
|
|
} else if (mode === 'edit' && id) {
|
|
const idx = posts.findIndex(p=>p.id===id);
|
|
if (idx !== -1) {
|
|
posts[idx].title = title;
|
|
posts[idx].content = content;
|
|
posts[idx].image = image;
|
|
posts[idx].journeyId = journeyId || null;
|
|
savePosts(posts);
|
|
renderPostList();
|
|
showPost(id);
|
|
}
|
|
}
|
|
|
|
closeForm();
|
|
});
|
|
});
|
|
|