Extend the notification system with a third channel (telegram) alongside existing in_app and push channels. Add `telegram_enabled` column to `notification_preferences` table, update `NOTIFICATION_CHANNELS` and `_NOTIFICATION_CHANNEL_COLUMNS` mappings, and set telegram default to off (`_NOTIFICATION_CHANNEL_DEFAULTS`). Create `telegram_outbox` table with columns for uid, user_uid, chat_id, text, status, attempts, created_at, and sent_at, plus an index on status/id for efficient polling. Register `TelegramOutboxService` in the service manager lifecycle. Update API documentation strings to describe the new channel and its pairing requirement. Extend admin notification defaults view to include telegram column. Pass `notif_telegram_paired` flag to profile template based on `telegram_store.is_paired()` check. Update `NotificationPrefForm` and `NotificationDefaultForm` model literals to accept "telegram" as a valid channel value.
75 lines
3.3 KiB
HTML
75 lines
3.3 KiB
HTML
{% extends "base.html" %}
|
|
{% block extra_head %}
|
|
<link rel="stylesheet" href="{{ static_url('/static/css/feed.css') }}">
|
|
<link rel="stylesheet" href="{{ static_url('/static/css/post.css') }}">
|
|
<link rel="stylesheet" href="{{ static_url('/static/css/news.css') }}">
|
|
{% endblock %}
|
|
{% block content %}
|
|
<div class="news-page">
|
|
<div class="news-header">
|
|
<h1>Developer News</h1>
|
|
<p class="news-subtitle">Curated signals for developers. Updated hourly.</p>
|
|
</div>
|
|
|
|
{% if articles %}
|
|
<div class="news-grid">
|
|
{% for item in articles %}
|
|
<article class="news-card card-link-host">
|
|
{% set _href = "/news/" ~ (item.article['slug'] or item.article['uid']) %}
|
|
{% set _label = item.article['title'] %}
|
|
{% include "_card_link.html" %}
|
|
{% if item.image_url %}
|
|
<div class="news-card-image">
|
|
<img src="{{ item.image_url }}" alt="{{ item.article['title'] }}" loading="lazy" class="image-fallback">
|
|
</div>
|
|
{% endif %}
|
|
<div class="news-card-body">
|
|
<div class="news-card-meta">
|
|
<span class="news-source">{{ item.article['source_name'] }}</span>
|
|
{% if is_admin(user) %}
|
|
{% if item.article.get('featured') %}
|
|
<span class="news-featured-badge">★ Featured</span>
|
|
{% endif %}
|
|
{% if item.grade >= 9 %}
|
|
<span class="news-grade news-grade-top">Grade {{ item.grade }}</span>
|
|
{% elif item.grade >= 7 %}
|
|
<span class="news-grade news-grade-high">Grade {{ item.grade }}</span>
|
|
{% else %}
|
|
<span class="news-grade">Grade {{ item.grade }}</span>
|
|
{% endif %}
|
|
{% endif %}
|
|
<span class="news-time">{{ dt_ago(item.synced_at) if item.synced_at else item.time_ago }}</span>
|
|
</div>
|
|
<h3 class="news-card-title">
|
|
<a href="/news/{{ item.article['slug'] or item.article['uid'] }}">{{ render_title(item.article['title']) }}</a>
|
|
</h3>
|
|
{% if item.article.get('description') %}
|
|
<p class="news-card-desc">{{ item.article['description'][:250] }}{% if item.article['description']|length > 250 %}...{% endif %}</p>
|
|
{% endif %}
|
|
{% if item.recent_comments %}
|
|
{% from "_comment.html" import render_comment with context %}
|
|
<div class="post-card-comments">
|
|
{% for c in item.recent_comments %}
|
|
{{ render_comment(c, 0) }}
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
<a href="{{ item.article['url'] }}" target="_blank" rel="noopener" class="news-read-link">
|
|
Read on {{ item.article['source_name'] }} ↗
|
|
</a>
|
|
</div>
|
|
</article>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
{% include "_load_more.html" %}
|
|
{% else %}
|
|
<div class="news-empty">
|
|
<div class="news-empty-icon">📰</div>
|
|
<h2>No news yet</h2>
|
|
<p>Check back soon for curated developer news.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|