first commit, mate!
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.category}} - Blog</title>
|
||||
<link rel="stylesheet" href="/static/css/material.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<div class="header__content">
|
||||
<a href="/" class="header__logo">📝 Blog</a>
|
||||
<nav class="header__nav">
|
||||
<a href="/">Home</a>
|
||||
<a href="#about">About</a>
|
||||
<a href="/feed">RSS Feed</a>
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container" style="padding: 40px 16px;">
|
||||
<div style="margin-bottom: 40px;">
|
||||
<h1>{{.category}}</h1>
|
||||
<p style="font-size: 18px; color: var(--md-sys-color-on-surface-variant);">
|
||||
Posts in the {{.category}} category
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="max-width: 800px;">
|
||||
{{if .posts}}
|
||||
<ul class="post-list">
|
||||
{{range .posts}}
|
||||
<li class="post-item">
|
||||
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 8px;">
|
||||
<h3 class="post-title">
|
||||
<a href="/post/{{.Slug}}">{{.Title}}</a>
|
||||
</h3>
|
||||
<span class="post-type {{if eq .Type "micro"}}post-type--micro{{end}}">
|
||||
{{if eq .Type "micro"}}💬{{else}}📝{{end}} {{.Type}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="post-meta">
|
||||
<span>{{.CreatedAt.Format "2006-01-02"}}</span>
|
||||
</div>
|
||||
<p class="post-excerpt">{{.Excerpt}}</p>
|
||||
<a href="/post/{{.Slug}}" style="font-weight: 500;">Read more →</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<div style="padding: 40px; text-align: center; color: var(--md-sys-color-on-surface-variant);">
|
||||
<p>No posts in this category yet.</p>
|
||||
<a href="/" class="button button--primary" style="margin-top: 16px;">← Back to Home</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer__content">
|
||||
<div class="footer__section">
|
||||
<h4>Category</h4>
|
||||
<p>Browsing posts in {{.category}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer__bottom">
|
||||
<p>© 2024. Self-hosted with care.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,247 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dashboard - Blog</title>
|
||||
<link rel="stylesheet" href="/static/css/material.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<div class="header__content">
|
||||
<a href="/" class="header__logo">📝 Blog</a>
|
||||
<nav class="header__nav">
|
||||
<a href="/">Home</a>
|
||||
<a href="#about">About</a>
|
||||
<a href="/feed">RSS Feed</a>
|
||||
<span style="padding: 8px 16px; background: rgba(255,255,255,0.2); border-radius: 20px;">Dashboard</span>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container" style="padding: 40px 16px;">
|
||||
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 40px; align-items: start;">
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside style="position: sticky; top: 20px;">
|
||||
<div class="card">
|
||||
<h3 style="margin-top: 0;">Dashboard</h3>
|
||||
<nav style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<button class="sidebar-btn active" data-view="editor" style="
|
||||
text-align: left;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
color: var(--md-sys-color-primary);
|
||||
font-weight: 500;
|
||||
">
|
||||
✍️ Write Post
|
||||
</button>
|
||||
<button class="sidebar-btn" data-view="posts" style="
|
||||
text-align: left;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
color: var(--md-sys-color-on-background);
|
||||
">
|
||||
📚 My Posts
|
||||
</button>
|
||||
<button class="sidebar-btn" data-view="stats" style="
|
||||
text-align: left;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
color: var(--md-sys-color-on-background);
|
||||
">
|
||||
📊 Statistics
|
||||
</button>
|
||||
<hr style="border: none; border-top: 1px solid var(--md-sys-color-outline-variant); margin: 12px 0;">
|
||||
<button onclick="handleLogout()" style="
|
||||
text-align: left;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
color: var(--md-sys-color-error);
|
||||
">
|
||||
🚪 Logout
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<section>
|
||||
<!-- Editor View -->
|
||||
<div id="editor-view" class="view-section" style="display: block;">
|
||||
<div id="post-editor"></div>
|
||||
</div>
|
||||
|
||||
<!-- Posts View -->
|
||||
<div id="posts-view" class="view-section" style="display: none;">
|
||||
<h2>My Posts</h2>
|
||||
<div id="user-posts-list" style="margin-top: 24px;">
|
||||
Loading your posts...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistics View -->
|
||||
<div id="stats-view" class="view-section" style="display: none;">
|
||||
<h2>Statistics</h2>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-top: 24px;">
|
||||
<div class="card">
|
||||
<div style="font-size: 12px; color: var(--md-sys-color-on-surface-variant); text-transform: uppercase; letter-spacing: 0.5px;">Total Posts</div>
|
||||
<div id="stat-total-posts" style="font-size: 32px; font-weight: 600; margin-top: 8px;">0</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div style="font-size: 12px; color: var(--md-sys-color-on-surface-variant); text-transform: uppercase; letter-spacing: 0.5px;">Published</div>
|
||||
<div id="stat-published-posts" style="font-size: 32px; font-weight: 600; margin-top: 8px;">0</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div style="font-size: 12px; color: var(--md-sys-color-on-surface-variant); text-transform: uppercase; letter-spacing: 0.5px;">Drafts</div>
|
||||
<div id="stat-draft-posts" style="font-size: 32px; font-weight: 600; margin-top: 8px;">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer__content">
|
||||
<div class="footer__section">
|
||||
<h4>Dashboard</h4>
|
||||
<p>Manage your blog posts and content from here.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer__bottom">
|
||||
<p>© 2024. Self-hosted with care.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/app.js"></script>
|
||||
<script>
|
||||
// View switching
|
||||
document.querySelectorAll('.sidebar-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const view = e.target.dataset.view;
|
||||
|
||||
// Update active button
|
||||
document.querySelectorAll('.sidebar-btn').forEach(b => {
|
||||
b.style.color = 'var(--md-sys-color-on-background)';
|
||||
});
|
||||
e.target.style.color = 'var(--md-sys-color-primary)';
|
||||
|
||||
// Update active view
|
||||
document.querySelectorAll('.view-section').forEach(section => {
|
||||
section.style.display = 'none';
|
||||
});
|
||||
document.getElementById(view + '-view').style.display = 'block';
|
||||
|
||||
// Load data for specific views
|
||||
if (view === 'posts') {
|
||||
loadUserPosts();
|
||||
} else if (view === 'stats') {
|
||||
loadStatistics();
|
||||
} else if (view === 'editor') {
|
||||
new PostEditor('post-editor');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize editor on first load
|
||||
new PostEditor('post-editor');
|
||||
|
||||
async function loadUserPosts() {
|
||||
try {
|
||||
const result = await api.get('/posts?page=1');
|
||||
const postsList = document.getElementById('user-posts-list');
|
||||
|
||||
if (!result.data || result.data.length === 0) {
|
||||
postsList.innerHTML = '<p>No posts yet. Start writing!</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<ul class="post-list">';
|
||||
result.data.forEach(post => {
|
||||
html += `
|
||||
<li class="post-item">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start;">
|
||||
<div style="flex: 1;">
|
||||
<h3 class="post-title" style="margin: 0 0 8px 0;">
|
||||
<a href="/post/${post.slug}">${post.title}</a>
|
||||
</h3>
|
||||
<div class="post-meta" style="font-size: 12px;">
|
||||
<span>${new Date(post.created_at).toLocaleDateString()}</span>
|
||||
${post.category ? `<span>${post.category}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button onclick="editPost('${post.id}')" class="button button--secondary" style="height: 32px; padding: 0 12px; font-size: 12px;">Edit</button>
|
||||
<button onclick="deletePost('${post.id}')" class="button button--tertiary" style="height: 32px; padding: 0 12px; font-size: 12px; color: var(--md-sys-color-error);">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
html += '</ul>';
|
||||
postsList.innerHTML = html;
|
||||
} catch (error) {
|
||||
document.getElementById('user-posts-list').innerHTML = '<p style="color: var(--md-sys-color-error);">Error loading posts</p>';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStatistics() {
|
||||
try {
|
||||
const result = await api.get('/posts?page=1');
|
||||
const posts = result.data || [];
|
||||
|
||||
const total = posts.length;
|
||||
const published = posts.filter(p => p.published).length;
|
||||
const drafts = total - published;
|
||||
|
||||
document.getElementById('stat-total-posts').textContent = total;
|
||||
document.getElementById('stat-published-posts').textContent = published;
|
||||
document.getElementById('stat-draft-posts').textContent = drafts;
|
||||
} catch (error) {
|
||||
console.error('Error loading statistics:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function editPost(postId) {
|
||||
Toast.show('Edit functionality coming soon', 'info');
|
||||
// TODO: Implement edit functionality
|
||||
}
|
||||
|
||||
async function deletePost(postId) {
|
||||
if (!confirm('Are you sure you want to delete this post?')) return;
|
||||
|
||||
try {
|
||||
const result = await api.delete(`/posts/${postId}`);
|
||||
if (result.success) {
|
||||
Toast.show('Post deleted', 'success');
|
||||
loadUserPosts();
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.show('Error deleting post', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
try {
|
||||
await api.post('/auth/logout', {});
|
||||
Toast.show('Logged out', 'success');
|
||||
setTimeout(() => window.location.href = '/', 1000);
|
||||
} catch (error) {
|
||||
Toast.show('Error logging out', 'error');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,73 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Blog - Kalvin's Corner</title>
|
||||
<link rel="stylesheet" href="/static/css/material.css">
|
||||
<link rel="alternate" type="application/rss+xml" title="Blog Feed" href="/feed">
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<div class="header__content">
|
||||
<a href="/" class="header__logo">📝 Blog</a>
|
||||
<nav class="header__nav">
|
||||
<a href="/">Home</a>
|
||||
<a href="#about">About</a>
|
||||
<a href="/feed">RSS Feed</a>
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container" style="padding: 40px 16px;">
|
||||
<div style="margin-bottom: 40px;">
|
||||
<h1>Welcome to the Blog</h1>
|
||||
<p style="font-size: 18px; color: var(--md-sys-color-on-surface-variant);">
|
||||
Essays, thoughts, and micro-posts on technology, digital sovereignty, and activism.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="search-box" style="margin-bottom: 40px; position: relative;">
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Search posts..."
|
||||
style="width: 100%; max-width: 500px;"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div id="posts-list">
|
||||
<!-- Posts will be loaded here -->
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer__content">
|
||||
<div class="footer__section">
|
||||
<h4>About This Blog</h4>
|
||||
<p>A self-hosted blogging platform for essays and thoughts on technology and activism.</p>
|
||||
</div>
|
||||
<div class="footer__section">
|
||||
<h4>Navigation</h4>
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/feed">RSS Feed</a></li>
|
||||
<li><a href="/dashboard">Dashboard</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer__section">
|
||||
<h4>Connect</h4>
|
||||
<ul>
|
||||
<li><a href="https://github.com">GitHub</a></li>
|
||||
<li><a href="https://social.obulou.org">Fediverse</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer__bottom">
|
||||
<p>© 2024. Self-hosted with care. <a href="https://github.com/kalvin0x8d0/blogging-cms">View source</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,134 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.post.Title}} - Blog</title>
|
||||
<link rel="stylesheet" href="/static/css/material.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<div class="header__content">
|
||||
<a href="/" class="header__logo">📝 Blog</a>
|
||||
<nav class="header__nav">
|
||||
<a href="/">Home</a>
|
||||
<a href="#about">About</a>
|
||||
<a href="/feed">RSS Feed</a>
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container" style="padding: 40px 16px; max-width: 800px;">
|
||||
<article>
|
||||
<header style="margin-bottom: 40px;">
|
||||
<h1 style="margin-top: 0;">{{.post.Title}}</h1>
|
||||
|
||||
<div class="post-meta" style="margin: 20px 0;">
|
||||
<span>By {{.author}}</span>
|
||||
<span>{{.post.CreatedAt.Format "2006-01-02"}}</span>
|
||||
{{if .post.Category}}
|
||||
<span><a href="/category/{{.post.Category}}">{{.post.Category}}</a></span>
|
||||
{{end}}
|
||||
<span>{{.commentCount}} comments</span>
|
||||
</div>
|
||||
|
||||
{{if .post.Tags}}
|
||||
<div class="tags">
|
||||
{{range .post.Tags}}
|
||||
<a href="/tag/{{.}}" class="tag">{{.}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</header>
|
||||
|
||||
<div style="
|
||||
line-height: 1.8;
|
||||
color: var(--md-sys-color-on-background);
|
||||
margin-bottom: 40px;
|
||||
">
|
||||
{{.post.Content}}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<hr style="border: none; border-top: 1px solid var(--md-sys-color-outline-variant); margin: 40px 0;">
|
||||
|
||||
<section style="margin: 40px 0;">
|
||||
<h2>Comments</h2>
|
||||
|
||||
<form id="comment-form" style="background: var(--md-sys-color-surface-variant); padding: 24px; border-radius: 12px; margin-bottom: 24px;">
|
||||
<div class="form-group">
|
||||
<label for="comment-content">Leave a comment</label>
|
||||
<textarea
|
||||
id="comment-content"
|
||||
name="content"
|
||||
placeholder="Your comment (will be moderated)"
|
||||
required
|
||||
style="min-height: 100px;"
|
||||
></textarea>
|
||||
</div>
|
||||
<button type="submit" class="button button--primary">Post Comment</button>
|
||||
</form>
|
||||
|
||||
<div id="comments-list">
|
||||
<p style="color: var(--md-sys-color-on-surface-variant); font-style: italic;">
|
||||
Comments are moderated and will appear after approval.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div style="margin-top: 60px; padding-top: 40px; border-top: 1px solid var(--md-sys-color-outline-variant);">
|
||||
<nav style="display: flex; justify-content: space-between;">
|
||||
<a href="/" class="button button--secondary">← Back to posts</a>
|
||||
</nav>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer__content">
|
||||
<div class="footer__section">
|
||||
<h4>About This Post</h4>
|
||||
<p>Published on {{.post.CreatedAt.Format "January 2, 2006"}}.
|
||||
{{if ne .post.UpdatedAt .post.CreatedAt}}
|
||||
Updated on {{.post.UpdatedAt.Format "January 2, 2006"}}.
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer__section">
|
||||
<h4>Share</h4>
|
||||
<ul>
|
||||
<li><a href="https://twitter.com/intent/tweet?url={{.post.Slug}}&text={{.post.Title}}" target="_blank">Share on X</a></li>
|
||||
<li><a href="https://www.linkedin.com/sharing/share-offsite/?url={{.post.Slug}}" target="_blank">Share on LinkedIn</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer__bottom">
|
||||
<p>© 2024. Self-hosted with care.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/app.js"></script>
|
||||
<script>
|
||||
document.getElementById('comment-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const content = document.getElementById('comment-content').value;
|
||||
const postID = '{{.post.ID}}';
|
||||
|
||||
try {
|
||||
const result = await api.post('/comments', {
|
||||
post_id: postID,
|
||||
content: content
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
Toast.show('Comment submitted! It will appear after moderation.', 'success');
|
||||
document.getElementById('comment-form').reset();
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.show('Error posting comment', 'error');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user