feat: add meta description and open graph tags for SEO optimization

- Inserted <meta name="description"> tag with site summary
- Added Open Graph (og:title, og:description, og:image) and Twitter Card meta tags to index.html head
- Ensures search engines and social platforms display rich previews
This commit is contained in:
2026-05-11 03:30:51 +00:00
parent ccb885607a
commit 66b6448850
49 changed files with 2116 additions and 188 deletions
+3
View File
@@ -0,0 +1,3 @@
<a href="/profile/{{ _user['username'] }}" class="user-avatar-link">
<img src="{{ avatar_url('multiavatar', _user['username'], _size) }}" class="avatar-img avatar-{{ _size_class }}" alt="{{ _user['username'] }}" loading="lazy">
</a>
+22
View File
@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block extra_head %}
<link rel="stylesheet" href="/static/css/admin.css">
{% endblock %}
{% block content %}
<div class="admin-layout">
<aside class="admin-sidebar">
<div class="admin-sidebar-header">Admin Panel</div>
<nav class="admin-sidebar-nav">
<a href="/admin/users" class="admin-nav-link {% if admin_section == 'users' %}active{% endif %}">
<span class="admin-nav-icon">&#x1F465;</span> Users
</a>
<a href="/admin/settings" class="admin-nav-link {% if admin_section == 'settings' %}active{% endif %}">
<span class="admin-nav-icon">&#x2699;&#xFE0F;</span> Settings
</a>
</nav>
</aside>
<main class="admin-content">
{% block admin_content %}{% endblock %}
</main>
</div>
{% endblock %}
+25
View File
@@ -0,0 +1,25 @@
{% extends "admin_base.html" %}
{% block admin_content %}
<div class="admin-toolbar">
<h2>Settings</h2>
</div>
<form method="POST" action="/admin/settings" class="admin-settings-form">
<div class="admin-field">
<label for="site_name">Site Name</label>
<input type="text" id="site_name" name="site_name" value="{{ settings.get('site_name', 'DevPlace') }}" maxlength="200">
</div>
<div class="admin-field">
<label for="site_description">Site Description</label>
<textarea id="site_description" name="site_description" rows="3" maxlength="500">{{ settings.get('site_description', 'The Developer Social Network') }}</textarea>
</div>
<div class="admin-field">
<label for="site_tagline">Tagline</label>
<input type="text" id="site_tagline" name="site_tagline" value="{{ settings.get('site_tagline', 'Track industry shifts. Discover bold releases. Share what you are building in an open, uncensored environment.') }}" maxlength="500">
</div>
<button type="submit" class="btn btn-primary">Save Settings</button>
</form>
{% endblock %}
+73
View File
@@ -0,0 +1,73 @@
{% extends "admin_base.html" %}
{% block admin_content %}
<div class="admin-toolbar">
<h2>Users</h2>
<span class="admin-count">{{ users|length }} users</span>
</div>
<div class="admin-table-wrap">
<table class="admin-table">
<thead>
<tr>
<th>Username</th>
<th>Email</th>
<th>Role</th>
<th>Posts</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for u in users %}
<tr>
<td>
<div class="admin-user-cell">
<img src="{{ avatar_url('multiavatar', u['username'], 24) }}" class="avatar-img" style="width: 24px; height: 24px; border-radius: 50%;" alt="">
<span>{{ u['username'] }}</span>
</div>
</td>
<td>{{ u['email'] }}</td>
<td>
{% if u['uid'] != user['uid'] %}
<form method="POST" action="/admin/users/{{ u['uid'] }}/role" class="admin-inline-form">
<select name="role" onchange="this.form.submit()" class="admin-select">
<option value="member" {% if u.get('role') == 'Member' %}selected{% endif %}>Member</option>
<option value="admin" {% if u.get('role') == 'Admin' %}selected{% endif %}>Admin</option>
</select>
</form>
{% else %}
<span class="admin-role-badge {{ u.get('role')|lower }}">{{ u.get('role', 'Member') }}</span>
{% endif %}
</td>
<td>{{ u.get('posts_count', 0) }}</td>
<td>
{% if u.get('is_active', True) %}
<span class="admin-status active">Active</span>
{% else %}
<span class="admin-status disabled">Disabled</span>
{% endif %}
</td>
<td>
<div class="admin-actions">
{% if u['uid'] != user['uid'] %}
<form method="POST" action="/admin/users/{{ u['uid'] }}/toggle" class="admin-inline-form">
<button type="submit" class="admin-btn admin-btn-sm">
{% if u.get('is_active', True) %}Disable{% else %}Enable{% endif %}
</button>
</form>
<button type="button" class="admin-btn admin-btn-sm" onclick="document.getElementById('pw-{{ u['uid'] }}').classList.toggle('hidden')">Password</button>
<form id="pw-{{ u['uid'] }}" method="POST" action="/admin/users/{{ u['uid'] }}/password" class="admin-pw-form hidden">
<input type="password" name="password" placeholder="New password" minlength="6" class="admin-input-sm">
<button type="submit" class="admin-btn admin-btn-sm">Set</button>
</form>
{% else %}
<span class="admin-text-muted">You</span>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
+56 -3
View File
@@ -3,11 +3,40 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DevPlace - The Developer Social Network</title>
<title>{% if page_title %}{{ page_title }}{% else %}DevPlace — The Developer Social Network{% endif %}</title>
<meta name="description" content="{% if meta_description %}{{ meta_description }}{% else %}Track industry shifts. Discover bold releases. Share what you're building in an open, uncensored environment.{% endif %}">
<link rel="canonical" href="{{ canonical_url or request.url }}">
<meta name="robots" content="{{ meta_robots or 'index,follow' }}">
<meta property="og:title" content="{{ og_title or page_title or 'DevPlace' }}">
<meta property="og:description" content="{{ og_description or meta_description or 'The Developer Social Network' }}">
<meta property="og:type" content="{{ og_type or 'website' }}">
<meta property="og:url" content="{{ canonical_url or request.url }}">
<meta property="og:image" content="{{ og_image }}">
<meta property="og:site_name" content="DevPlace">
<meta property="og:locale" content="en_US">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ og_title or page_title or 'DevPlace' }}">
<meta name="twitter:description" content="{{ og_description or meta_description or 'The Developer Social Network' }}">
<meta name="twitter:image" content="{{ og_image }}">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>&#x1f4bb;</text></svg>">
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
<link rel="preconnect" href="https://cdn.jsdelivr.net">
<link rel="stylesheet" href="/static/css/variables.css">
<link rel="stylesheet" href="/static/css/base.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>&#x1f4bb;</text></svg>">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/atom-one-dark.min.css">
<link rel="stylesheet" href="/static/css/markdown.css">
{% block extra_head %}{% endblock %}
{% if page_schema %}
<script type="application/ld+json">
{{ page_schema | safe }}
</script>
{% endif %}
</head>
<body>
{% if user %}
@@ -18,6 +47,9 @@
<a href="/feed" class="topnav-link {% if request.url.path == '/feed' %}active{% endif %}">Home</a>
<a href="/projects" class="topnav-link {% if 'projects' in request.url.path %}active{% endif %}">Projects</a>
<a href="/messages" class="topnav-link {% if 'messages' in request.url.path %}active{% endif %}">Messages</a>
{% if user.get('role') == 'Admin' %}
<a href="/admin" class="topnav-link {% if 'admin' in request.url.path %}active{% endif %}">Admin</a>
{% endif %}
</div>
<div class="topnav-right">
<a href="/notifications" class="topnav-icon">
@@ -28,7 +60,7 @@
{% endif %}
</a>
<a href="/profile/{{ user['username'] }}" class="topnav-user">
<img src="{{ avatar_url('multiavatar', user['username'], 32) }}" class="avatar-img avatar-sm" alt="{{ user['username'] }}">
{% set _user = user %}{% set _size = 32 %}{% set _size_class = "sm" %}{% include "_avatar_link.html" %}
<div class="topnav-user-info">
<span class="topnav-user-name">{{ user['username'] }}</span>
<span class="topnav-user-role">{{ user.get('role', 'Member') }}</span>
@@ -39,10 +71,31 @@
</nav>
{% endif %}
{% if breadcrumbs and breadcrumbs|length > 1 %}
<nav aria-label="Breadcrumb" class="breadcrumb">
<ol>
{% for crumb in breadcrumbs %}
<li>
{% if not loop.last %}
<a href="{{ crumb.url }}">{{ crumb.name }}</a>
{% else %}
<span aria-current="page">{{ crumb.name }}</span>
{% endif %}
</li>
{% endfor %}
</ol>
</nav>
{% endif %}
<main class="page">
{% block content %}{% endblock %}
</main>
<script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.umd.js"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js"></script>
<script src="/static/js/ContentRenderer.js"></script>
<script src="/static/js/EmojiPicker.js"></script>
<script type="module" src="/static/js/Application.js"></script>
{% block extra_js %}{% endblock %}
</body>
+22 -32
View File
@@ -3,6 +3,7 @@
<link rel="stylesheet" href="/static/css/feed.css">
{% endblock %}
{% block content %}
<h1 class="sr-only">Feed</h1>
<div class="feed-layout">
<aside class="feed-sidebar">
<h3>Topics</h3>
@@ -11,24 +12,24 @@
<span class="dot" style="background: var(--text-muted);"></span>
All
</a>
<a href="/feed?topic=devlog" class="feed-category {% if current_topic == 'devlog' %}active{% endif %}">
<span class="dot" style="background: #7c4dff;"></span>
<a href="/feed?topic=devlog" class="feed-category {% if current_topic == 'devlog' %}active{% endif %}" data-topic="devlog">
<span class="dot"></span>
Devlog
</a>
<a href="/feed?topic=showcase" class="feed-category {% if current_topic == 'showcase' %}active{% endif %}">
<span class="dot" style="background: #00bfa5;"></span>
<a href="/feed?topic=showcase" class="feed-category {% if current_topic == 'showcase' %}active{% endif %}" data-topic="showcase">
<span class="dot"></span>
Showcase
</a>
<a href="/feed?topic=question" class="feed-category {% if current_topic == 'question' %}active{% endif %}">
<span class="dot" style="background: #448aff;"></span>
<a href="/feed?topic=question" class="feed-category {% if current_topic == 'question' %}active{% endif %}" data-topic="question">
<span class="dot"></span>
Question
</a>
<a href="/feed?topic=rant" class="feed-category {% if current_topic == 'rant' %}active{% endif %}">
<span class="dot" style="background: #ff5252;"></span>
<a href="/feed?topic=rant" class="feed-category {% if current_topic == 'rant' %}active{% endif %}" data-topic="rant">
<span class="dot"></span>
Rant
</a>
<a href="/feed?topic=fun" class="feed-category {% if current_topic == 'fun' %}active{% endif %}">
<span class="dot" style="background: #ffab00;"></span>
<a href="/feed?topic=fun" class="feed-category {% if current_topic == 'fun' %}active{% endif %}" data-topic="fun">
<span class="dot"></span>
Fun
</a>
</div>
@@ -50,9 +51,9 @@
{% for item in posts %}
<article class="post-card fade-in">
<div class="post-header">
<img src="{{ avatar_url('multiavatar', item.author['username'] if item.author else '?', 32) }}" class="avatar-img avatar-sm" alt="{{ item.author['username'] if item.author else '?' }}">
{% set _user = item.author %}{% set _size = 32 %}{% set _size_class = "sm" %}{% include "_avatar_link.html" %}
<div class="post-author-wrap">
<span class="post-author">{{ item.author['username'] if item.author else 'Unknown' }}</span>
<a href="/profile/{{ item.author['username'] if item.author else '#' }}" class="post-author-link">{{ item.author['username'] if item.author else 'Unknown' }}</a>
{% if item.author and item.author.get('role') %}
<span class="post-author-role">{{ item.author['role'] }}</span>
{% endif %}
@@ -68,7 +69,7 @@
<h3 class="post-title">{{ item.post['title'] }}</h3>
{% endif %}
<p class="post-content">{{ item.post['content'][:300] }}{% if item.post['content']|length > 300 %}...{% endif %}</p>
<div class="post-content rendered-content" data-render>{{ item.post['content'][:300] }}{% if item.post['content']|length > 300 %}...{% endif %}</div>
<div class="post-actions">
<form method="POST" action="/votes/post/{{ item.post['uid'] }}" style="display:inline;">
@@ -128,11 +129,14 @@
{% for author in top_authors %}
<div class="stat-row">
<span class="label">
<img src="{{ avatar_url('multiavatar', author['username'], 20) }}" class="avatar-img" style="width: 20px; height: 20px; vertical-align: middle; margin-right: 0.375rem;" alt="{{ author['username'] }}">
{{ author['username'] }}
<a href="/profile/{{ author['username'] }}">
<img src="{{ avatar_url('multiavatar', author['username'], 20) }}" class="avatar-img" style="width: 20px; height: 20px; vertical-align: middle; margin-right: 0.375rem;" alt="{{ author['username'] }}" loading="lazy">
{{ author['username'] }}</a>
</span>
<span class="value">{{ author.get('stars', 0) }}</span>
</div>
{% else %}
<div style="font-size: 0.75rem; color: var(--text-muted); padding: 0.5rem 0;">No top authors yet</div>
{% endfor %}
</div>
</div>
@@ -141,7 +145,7 @@
<a href="#" class="feed-fab" data-modal="create-post-modal" title="Create New Post">+</a>
<div class="modal-overlay" id="create-post-modal" style="display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 2000; align-items: center; justify-content: center;">
<div class="modal-overlay" id="create-post-modal">
<div class="card" style="width: 100%; max-width: 560px; margin: 1rem; max-height: 90vh; overflow-y: auto;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h3 style="font-size: 1.125rem; font-weight: 700;">Create New Post</h3>
@@ -161,13 +165,13 @@
<div class="auth-field" style="margin-bottom: 0.75rem;">
<label for="post-content">What are you sharing?</label>
<textarea id="post-content" name="content" required maxlength="2000" placeholder="What's on your mind?" style="min-height: 120px;"></textarea>
<small style="color: var(--text-muted); font-size: 0.75rem;"><span id="post-content-count">0/2000</span></small>
<small class="hint-text"><span id="post-content-count">0/2000</span></small>
</div>
<div class="auth-field" style="margin-bottom: 0.75rem;">
<label for="post-title">Title (optional)</label>
<input type="text" id="post-title" name="title" maxlength="500" placeholder="Post title">
<small style="color: var(--text-muted); font-size: 0.75rem;"><span id="post-title-count">0/500</span></small>
<small class="hint-text"><span id="post-title-count">0/500</span></small>
</div>
<div class="auth-field" style="margin-bottom: 1rem;">
@@ -193,18 +197,4 @@
</div>
</div>
<script>
document.querySelector("[data-modal]").addEventListener("click", function() {
document.getElementById("create-post-modal").style.display = "flex";
});
document.querySelectorAll(".modal-close").forEach(function(btn) {
btn.addEventListener("click", function() {
var modal = this.closest(".modal-overlay");
if (modal) modal.style.display = "none";
});
});
document.getElementById("create-post-modal").addEventListener("click", function(e) {
if (e.target === this) this.style.display = "none";
});
</script>
{% endblock %}
+1 -1
View File
@@ -10,7 +10,7 @@
<p class="subtitle">Enter your email and we will send you a reset link</p>
{% if sent %}
<div class="auth-success" style="background: rgba(76, 175, 80, 0.1); border: 1px solid var(--success); border-radius: var(--radius); padding: 0.75rem; font-size: 0.8125rem; color: var(--success); margin-bottom: 0.5rem;">
<div class="auth-success">
If an account with that email exists, a reset link has been sent. Check your email or server logs.
</div>
{% endif %}
+9 -8
View File
@@ -1,12 +1,10 @@
{% extends "base.html" %}
{% block extra_head %}
<link rel="stylesheet" href="/static/css/messages.css">
<style>
.page { padding-top: 0; }
.page > .messages-layout { margin-top: calc(var(--nav-height) + 1rem); }
</style>
{% endblock %}
{% block content %}
<h1 class="sr-only">Messages</h1>
<div class="page-messages">
<div class="messages-layout">
<div class="messages-list">
<div class="messages-list-header">
@@ -16,7 +14,7 @@
<div class="messages-conversations">
{% for conv in conversations %}
<a href="/messages?with_uid={{ conv.other_user['uid'] }}" class="conversation-item {% if current_conversation == conv.other_user['uid'] %}active{% endif %}">
<img src="{{ avatar_url('multiavatar', conv.other_user['username'], 32) }}" class="avatar-img avatar-sm" alt="{{ conv.other_user['username'] }}">
<img src="{{ avatar_url('multiavatar', conv.other_user['username'], 32) }}" class="avatar-img avatar-sm" alt="{{ conv.other_user['username'] }}" loading="lazy">
<div class="conversation-info">
<div class="conversation-name">{{ conv.other_user['username'] }}</div>
<div class="conversation-preview">{{ conv.last_message[:60] }}</div>
@@ -32,14 +30,16 @@
<div class="messages-main">
{% if other_user %}
<div class="messages-main-header">
<img src="{{ avatar_url('multiavatar', other_user['username'], 32) }}" class="avatar-img avatar-sm" alt="{{ other_user['username'] }}">
<h3>{{ other_user['username'] }}</h3>
<a href="/profile/{{ other_user['username'] }}">
<img src="{{ avatar_url('multiavatar', other_user['username'], 32) }}" class="avatar-img avatar-sm" alt="{{ other_user['username'] }}" loading="lazy">
</a>
<a href="/profile/{{ other_user['username'] }}"><h3>{{ other_user['username'] }}</h3></a>
</div>
<div class="messages-thread">
{% for msg in messages %}
<div class="message-bubble {% if msg.is_mine %}mine{% else %}theirs{% endif %}">
{{ msg.message['content'] }}
<div class="rendered-content" data-render>{{ msg.message['content'] }}</div>
<span class="message-time">{{ msg.time_ago }}</span>
</div>
{% endfor %}
@@ -57,4 +57,5 @@
{% endif %}
</div>
</div>
</div>
{% endblock %}
+3 -1
View File
@@ -14,7 +14,9 @@
<div class="notifications-list">
{% for item in notifications %}
<div class="notification-card {% if not item.notification['read'] %}unread{% endif %}">
<img src="{{ avatar_url('multiavatar', item.actor['username'] if item.actor else '?', 32) }}" class="avatar-img avatar-sm" alt="{{ item.actor['username'] if item.actor else '?' }}">
<a href="/profile/{{ item.actor['username'] if item.actor else '#' }}">
<img src="{{ avatar_url('multiavatar', item.actor['username'] if item.actor else '?', 32) }}" class="avatar-img avatar-sm" alt="{{ item.actor['username'] if item.actor else '?' }}" loading="lazy">
</a>
<div class="notification-body">
<div class="notification-text">{{ item.notification['message'] }}</div>
<div class="notification-time">{{ item.time_ago }}</div>
+38 -19
View File
@@ -8,13 +8,14 @@
<article class="post-detail">
<div class="post-detail-header">
<img src="{{ avatar_url('multiavatar', author['username'] if author else '?', 40) }}" class="avatar-img" style="width: 40px; height: 40px; border-radius: 50%;" alt="{{ author['username'] if author else '?' }}">
<a href="/profile/{{ author['username'] if author else '#' }}">
<img src="{{ avatar_url('multiavatar', author['username'] if author else '?', 40) }}" class="avatar-img" style="width: 40px; height: 40px; border-radius: 50%;" alt="{{ author['username'] if author else '?' }}" loading="lazy">
</a>
<div>
<div class="post-detail-author">{{ author['username'] if author else 'Unknown' }}
{% if author and author.get('role') %}
<span class="post-detail-role">&middot; {{ author['role'] }}</span>
{% endif %}
</div>
<a href="/profile/{{ author['username'] if author else '#' }}" class="post-detail-author">{{ author['username'] if author else 'Unknown' }}</a>
{% if author and author.get('role') %}
<span class="post-detail-role">&middot; {{ author['role'] }}</span>
{% endif %}
</div>
<span class="post-detail-time">{{ time_ago }}</span>
</div>
@@ -27,16 +28,19 @@
<h1 class="post-detail-title">{{ post['title'] }}</h1>
{% endif %}
<div class="post-detail-content">{{ post['content'] }}</div>
<div class="post-detail-content rendered-content" data-render>{{ post['content'] }}</div>
<div class="post-detail-actions">
<form method="POST" action="/votes/post/{{ post['uid'] }}" style="display:inline;">
<input type="hidden" name="value" value="1">
<button type="submit" class="post-action-btn" style="display: flex; align-items: center; gap: 0.25rem; padding: 0.375rem 0.625rem; border-radius: var(--radius); font-size: 0.8125rem; color: var(--text-muted); background: none; cursor: pointer;">
+{{ post.get('stars', 0) }}
</button>
<button type="submit" class="post-action-btn vote-up">+{{ post.get('stars', 0) }}</button>
</form>
<button class="post-action-btn" style="display: flex; align-items: center; gap: 0.25rem; padding: 0.375rem 0.625rem; border-radius: var(--radius); font-size: 0.8125rem; color: var(--text-muted); background: none;">&#x1F517; Share</button>
<button class="post-action-btn">&#x1F517; Share</button>
{% if user and post['user_uid'] == user['uid'] %}
<form method="POST" action="/posts/delete/{{ post['uid'] }}" style="display:inline;">
<button type="submit" class="post-action-btn" onclick="return confirm('Delete this post?')">Delete</button>
</form>
{% endif %}
</div>
</article>
@@ -45,7 +49,6 @@
{% macro render_comment(item, depth=0) %}
<div class="comment" style="margin-left: {{ depth * 1.5 }}rem;">
<div class="comment-thread-line"></div>
<div class="comment-votes">
<form method="POST" action="/votes/comment/{{ item.comment['uid'] }}">
<input type="hidden" name="value" value="1">
@@ -60,11 +63,13 @@
<div class="comment-body">
<div class="comment-header">
<img src="{{ avatar_url('multiavatar', item.author['username'] if item.author else '?', 24) }}" class="avatar-img" style="width: 24px; height: 24px; border-radius: 50%;" alt="{{ item.author['username'] if item.author else '?' }}">
<span class="comment-author">{{ item.author['username'] if item.author else 'Unknown' }}</span>
<a href="/profile/{{ item.author['username'] if item.author else '#' }}">
<img src="{{ avatar_url('multiavatar', item.author['username'] if item.author else '?', 24) }}" class="avatar-img" style="width: 24px; height: 24px; border-radius: 50%;" alt="{{ item.author['username'] if item.author else '?' }}" loading="lazy">
</a>
<a href="/profile/{{ item.author['username'] if item.author else '#' }}" class="comment-author">{{ item.author['username'] if item.author else 'Unknown' }}</a>
<span class="comment-time">{{ item.time_ago }}</span>
</div>
<div class="comment-text">{{ item.comment['content'] }}</div>
<div class="comment-text rendered-content" data-render>{{ item.comment['content'] }}</div>
<div class="comment-actions">
<button class="comment-action-btn">Reply</button>
{% if user and item.comment['user_uid'] == user['uid'] %}
@@ -94,15 +99,29 @@
{% if user %}
<form class="comment-form" method="POST" action="/comments/create">
<input type="hidden" name="post_uid" value="{{ post['uid'] }}">
<img src="{{ avatar_url('multiavatar', user['username'], 32) }}" class="avatar-img avatar-sm" alt="{{ user['username'] }}">
<textarea name="content" placeholder="Your opinion goes here..." required maxlength="1000"></textarea>
<a href="/profile/{{ user['username'] }}">
<img src="{{ avatar_url('multiavatar', user['username'], 32) }}" class="avatar-img avatar-sm" alt="{{ user['username'] }}" loading="lazy">
</a>
<textarea name="content" placeholder="Your opinion goes here..." required maxlength="1000" class="emoji-picker-target"></textarea>
<div class="comment-form-actions">
<button type="button" title="Add emoji">&#x1F600;</button>
<button type="button" title="Add image">&#x1F5BC;</button>
<button type="submit" class="comment-form-submit">Post</button>
</div>
</form>
{% endif %}
</section>
{% if related_posts %}
<section class="comments-section" style="margin-top: 1.5rem;">
<h3>Related Discussions</h3>
<div style="display: flex; flex-direction: column; gap: 0.75rem;">
{% for item in related_posts %}
<a href="/posts/{{ item.post['uid'] }}" class="post-action-btn" style="flex-direction: column; align-items: flex-start; gap: 0.25rem; padding: 0.75rem; width: 100%;">
<strong style="color: var(--text-primary); font-size: 0.875rem;">{{ item.post.get('title') or item.post['content'][:60] }}</strong>
<span style="color: var(--text-muted); font-size: 0.75rem;">{{ item.author['username'] }} · {{ item.time_ago }}</span>
</a>
{% endfor %}
</div>
</section>
{% endif %}
</div>
{% endblock %}
+10 -7
View File
@@ -1,5 +1,6 @@
{% extends "base.html" %}
{% block extra_head %}
<link rel="stylesheet" href="/static/css/feed.css">
<link rel="stylesheet" href="/static/css/profile.css">
<link rel="stylesheet" href="/static/css/projects.css">
<style>
@@ -11,9 +12,9 @@
<aside class="profile-sidebar">
<div class="profile-card">
<div class="profile-avatar-wrap">
<img src="{{ avatar_url('multiavatar', profile_user['username'], 80) }}" class="avatar-img avatar-lg" alt="{{ profile_user['username'] }}" id="profile-avatar-preview">
<img src="{{ avatar_url('multiavatar', profile_user['username'], 80) }}" class="avatar-img avatar-lg" alt="{{ profile_user['username'] }}" id="profile-avatar-preview" loading="lazy">
</div>
<div class="profile-name">{{ profile_user['username'] }}</div>
<h1 class="profile-name">{{ profile_user['username'] }}</h1>
{% if profile_user.get('role') %}
<div class="profile-role">{{ profile_user['role'] }}</div>
{% endif %}
@@ -139,7 +140,7 @@
</aside>
<div class="profile-content">
<a href="/feed" style="display: inline-flex; align-items: center; gap: 0.375rem; color: var(--text-secondary); font-size: 0.875rem; margin-bottom: 1rem;">&larr; Back</a>
<a href="/feed" class="back-link">&larr; Back</a>
<div class="profile-tabs">
<a href="/profile/{{ profile_user['username'] }}?tab=posts" class="profile-tab {% if current_tab == 'posts' %}active{% endif %}">Posts</a>
@@ -153,9 +154,11 @@
{% for item in posts %}
<article class="post-card fade-in">
<div class="post-header">
<img src="{{ avatar_url('multiavatar', profile_user['username'], 32) }}" class="avatar-img avatar-sm" alt="{{ profile_user['username'] }}">
<a href="/profile/{{ profile_user['username'] }}">
<img src="{{ avatar_url('multiavatar', profile_user['username'], 32) }}" class="avatar-img avatar-sm" alt="{{ profile_user['username'] }}" loading="lazy">
</a>
<div class="post-author-wrap">
<span class="post-author">{{ profile_user['username'] }}</span>
<a href="/profile/{{ profile_user['username'] }}" class="post-author-link">{{ profile_user['username'] }}</a>
{% if profile_user.get('role') %}
<span class="post-author-role">{{ profile_user['role'] }}</span>
{% endif %}
@@ -168,7 +171,7 @@
{% if item.post.get('title') %}
<h3 class="post-title">{{ item.post['title'] }}</h3>
{% endif %}
<p class="post-content">{{ item.post['content'][:200] }}{% if item.post['content']|length > 200 %}...{% endif %}</p>
<div class="post-content rendered-content" data-render>{{ item.post['content'][:200] }}{% if item.post['content']|length > 200 %}...{% endif %}</div>
<div class="post-actions">
<span class="post-action-btn" style="cursor: default;">+{{ item.post.get('stars', 0) }}</span>
<span class="post-action-btn" style="cursor: default;">&#x1F4AC; {{ item.comment_count }}</span>
@@ -190,7 +193,7 @@
</span>
<span>{{ p.get('project_type', 'software')|replace('_', ' ')|capitalize }}</span>
</div>
<div class="project-card-desc">{{ p.get('description', '')[:200] }}</div>
<div class="project-card-desc rendered-content" data-render>{{ p.get('description', '')[:200] }}</div>
{% if p.get('platforms') %}
<div class="project-card-platforms">
{% for plat in p['platforms'].split(',') %}
+3 -7
View File
@@ -1,10 +1,6 @@
{% extends "base.html" %}
{% block extra_head %}
<link rel="stylesheet" href="/static/css/projects.css">
<style>
.modal-overlay { display: none; }
.modal-overlay.visible { display: flex; }
</style>
{% endblock %}
{% block content %}
<div class="projects-layout">
@@ -38,7 +34,7 @@
<div class="projects-main">
<div class="projects-header">
<div>
<h2>Projects</h2>
<h1>Projects</h1>
<span class="subtitle">Discover amazing projects from the DevPlace community</span>
</div>
<div class="projects-count">Showing {{ total_count }} of {{ total_count }} projects</div>
@@ -75,7 +71,7 @@
</span>
</div>
<div class="project-card-desc">{{ project.get('description', '')[:200] }}</div>
<div class="project-card-desc rendered-content" data-render>{{ project.get('description', '')[:200] }}</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span class="badge" style="background: var(--border); color: var(--text-muted); text-transform: capitalize;">{{ project.get('project_type', 'software').replace('_', ' ') }}</span>
@@ -97,7 +93,7 @@
<button class="feed-fab" id="create-project-btn" title="Add Project" style="position: fixed; bottom: 2rem; right: 2rem; width: 56px; height: 56px; border-radius: 50%; background: var(--accent); color: #fff; font-size: 1.5rem; display: flex; align-items: center; justify-content: center; box-shadow: var(--shadow-lg); border: none; cursor: pointer; z-index: 100;">+</button>
<div class="modal-overlay" id="create-project-modal" style="position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 2000; align-items: center; justify-content: center;">
<div class="modal-overlay" id="create-project-modal">
<div class="card" style="width: 100%; max-width: 560px; margin: 1rem; max-height: 90vh; overflow-y: auto;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h3 style="font-size: 1.125rem; font-weight: 700;">Create Project</h3>
+1 -1
View File
@@ -23,7 +23,7 @@
<div class="auth-field">
<label for="username">Username</label>
<input type="text" id="username" name="username" value="{{ username or '' }}" required maxlength="32" placeholder="cooldev42">
<small style="color: var(--text-muted); font-size: 0.75rem;">Letters, numbers, hyphens, and underscores only</small>
<small class="hint-text">Letters, numbers, hyphens, and underscores only</small>
</div>
<div class="auth-field">