Files
balanceboard/templates/settings_communities.html
chelsea b47155cc36 Fix Stack Overflow crawling platform name mismatch
The issue was that Stack Overflow was configured with platform name
'stackoverflow' but the data collection code expected 'stackexchange'.
Fixed by:

1. Renamed platform from 'stackoverflow' to 'stackexchange' in platform_config.json
2. Added Stack Overflow collection target to enable crawling
3. Updated templates and app.py to use the correct platform name
4. Added default 'stackoverflow' community alongside existing featured/newest

This resolves the platform name mismatch that prevented Stack Overflow
from being crawlable.

Fixes #23

~claude

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 03:07:41 -05:00

359 lines
10 KiB
HTML

{% extends "base.html" %}
{% block title %}Community Settings - BalanceBoard{% endblock %}
{% block extra_css %}
<style>
.settings-container {
max-width: 1000px;
margin: 0 auto;
padding: 24px;
}
.settings-header {
margin-bottom: 32px;
}
.settings-header h1 {
color: var(--text-primary);
margin-bottom: 8px;
}
.settings-header p {
color: var(--text-secondary);
font-size: 1.1rem;
}
.settings-content {
background: var(--surface-color);
border-radius: 12px;
padding: 32px;
border: 1px solid var(--border-color);
}
.community-section {
margin-bottom: 32px;
}
.community-section:last-child {
margin-bottom: 0;
}
.community-section h2 {
color: var(--text-primary);
margin-bottom: 16px;
font-size: 1.3rem;
}
.community-section p {
color: var(--text-secondary);
margin-bottom: 24px;
line-height: 1.6;
}
.platform-group {
margin-bottom: 32px;
}
.platform-group h3 {
color: var(--text-primary);
margin-bottom: 16px;
font-size: 1.1rem;
display: flex;
align-items: center;
gap: 8px;
}
.platform-icon {
width: 24px;
height: 24px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
color: white;
font-size: 0.8rem;
}
.platform-icon.reddit { background: #ff4500; }
.platform-icon.hackernews { background: #ff6600; }
.platform-icon.lobsters { background: #ac130d; }
.platform-icon.stackexchange { background: #f48024; }
.community-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.community-item {
background: var(--surface-elevation-1);
border: 2px solid var(--border-color);
border-radius: 8px;
padding: 16px;
transition: all 0.2s ease;
cursor: pointer;
}
.community-item:hover {
border-color: var(--primary-color);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.community-item.selected {
border-color: var(--primary-color);
background: rgba(77, 182, 172, 0.1);
}
.community-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
.community-checkbox {
width: 20px;
height: 20px;
accent-color: var(--primary-color);
}
.community-info h4 {
color: var(--text-primary);
margin-bottom: 4px;
font-size: 1rem;
}
.community-info p {
color: var(--text-secondary);
font-size: 0.9rem;
margin: 0;
}
.community-meta {
display: flex;
align-items: center;
gap: 16px;
margin-top: 8px;
font-size: 0.85rem;
color: var(--text-secondary);
}
.community-meta span {
display: flex;
align-items: center;
gap: 4px;
}
.form-actions {
display: flex;
gap: 12px;
padding-top: 24px;
border-top: 1px solid var(--divider-color);
}
.btn-primary {
padding: 12px 24px;
background: var(--primary-color);
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-primary:hover {
background: var(--primary-hover);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(77, 182, 172, 0.3);
}
.btn-secondary {
background: var(--surface-elevation-1);
color: var(--text-primary);
}
.btn-secondary:hover {
background: var(--surface-elevation-2);
}
.selected-summary {
background: var(--surface-elevation-1);
border-radius: 8px;
padding: 16px;
margin-bottom: 24px;
border-left: 4px solid var(--primary-color);
}
.selected-summary h3 {
color: var(--text-primary);
margin-bottom: 8px;
}
.selected-summary p {
color: var(--text-secondary);
margin: 0;
}
.flash-messages {
margin-bottom: 24px;
}
.flash-message {
padding: 12px 16px;
border-radius: 8px;
margin-bottom: 12px;
font-size: 0.95rem;
}
.flash-message.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.flash-message.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
@media (max-width: 768px) {
.community-grid {
grid-template-columns: 1fr;
}
.form-actions {
flex-direction: column;
}
}
</style>
{% endblock %}
{% block content %}
{% include '_nav.html' %}
<div class="settings-container">
<div class="settings-header">
<h1>Community Settings</h1>
<p>Select which communities, subreddits, and sources to include in your feed</p>
</div>
<div class="settings-content">
<div class="flash-messages">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="flash-message {{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<form method="POST">
<div class="selected-summary">
<h3>Current Selection</h3>
<p>You have selected <strong>{{ selected_communities|length }}</strong> communities out of <strong>{{ available_communities|length }}</strong> available.</p>
</div>
<div class="community-section">
<h2>Available Communities</h2>
<p>Choose the communities you want to follow. Content from these sources will appear in your feed.</p>
{% set platforms = available_communities|groupby('platform') %}
{% for platform, communities in platforms %}
<div class="platform-group">
<h3>
<span class="platform-icon {{ platform }}">
{% if platform == 'reddit' %}R{% elif platform == 'hackernews' %}H{% elif platform == 'lobsters' %}L{% elif platform == 'stackexchange' %}S{% endif %}
</span>
{{ platform|title }}
</h3>
<div class="community-grid">
{% for community in communities %}
<div class="community-item {% if community.id in selected_communities %}selected{% endif %}"
onclick="toggleCommunity(this, '{{ community.id }}')">
<div class="community-header">
<input type="checkbox"
name="communities"
value="{{ community.id }}"
class="community-checkbox"
{% if community.id in selected_communities %}checked{% endif %}
onclick="event.stopPropagation()">
<div class="community-info">
<h4>{{ community.name }}</h4>
<p>{{ community.platform|title }} community</p>
</div>
</div>
<div class="community-meta">
<span>📊 {{ community.platform|title }}</span>
<span>🔗 {{ community.id }}</span>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
<div class="form-actions">
<button type="submit" class="btn-primary">Save Community Preferences</button>
<a href="{{ url_for('settings') }}" class="btn-primary btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
<script>
function toggleCommunity(element, communityId) {
const checkbox = element.querySelector('.community-checkbox');
checkbox.checked = !checkbox.checked;
if (checkbox.checked) {
element.classList.add('selected');
} else {
element.classList.remove('selected');
}
updateSummary();
}
function updateSummary() {
const checkedBoxes = document.querySelectorAll('.community-checkbox:checked');
const totalBoxes = document.querySelectorAll('.community-checkbox');
const summary = document.querySelector('.selected-summary p');
summary.innerHTML = `You have selected <strong>${checkedBoxes.length}</strong> communities out of <strong>${totalBoxes.length}</strong> available.`;
}
// Prevent form submission when clicking on community items
document.querySelectorAll('.community-item').forEach(item => {
item.addEventListener('click', function(e) {
if (e.target.type !== 'checkbox') {
const checkbox = this.querySelector('.community-checkbox');
const communityId = checkbox.value;
toggleCommunity(this, communityId);
}
});
});
// Update summary on checkbox change
document.querySelectorAll('.community-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', function() {
const item = this.closest('.community-item');
if (this.checked) {
item.classList.add('selected');
} else {
item.classList.remove('selected');
}
updateSummary();
});
});
</script>
{% endblock %}