Fix broken reference links and web UI bugs

- Fix RFC URLs with leading zeros (rfc0020 -> rfc20) via int filter
- Draft refs: internal link for drafts in our DB, Datatracker for external
- BCP refs: link to rfc-editor.org/info/bcpN
- Add DB connection teardown (@app.teardown_appcontext)
- Fix JS syntax error in gap_demo.html (HTML-escaped string in script tag)
- Add URL encoding to all query params in drafts.html and draft_detail.html
- Fix variable shadowing of Flask's g import in gaps_demo()
- Add None safety for ideas search data attribute

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 07:59:45 +01:00
parent 6e3a387778
commit 75c4da72e0
6 changed files with 127 additions and 34 deletions

View File

@@ -256,7 +256,7 @@
</h2>
<div class="flex flex-wrap gap-1.5">
{% for cat in draft.rating.categories %}
<a href="/drafts?cat={{ cat }}"
<a href="/drafts?cat={{ cat | urlencode }}"
class="px-2.5 py-1 rounded-full text-xs bg-slate-800/60 text-slate-400 border border-slate-700 hover:border-blue-500 hover:text-blue-400 transition">
{{ cat }}
</a>
@@ -275,16 +275,28 @@
<div class="flex flex-wrap gap-1.5 max-h-48 overflow-y-auto">
{% for ref in draft.refs %}
{% if ref.type == 'rfc' %}
<a href="https://www.rfc-editor.org/rfc/{{ ref.id }}" target="_blank" rel="noopener"
<a href="https://www.rfc-editor.org/rfc/rfc{{ ref.id | int }}" target="_blank" rel="noopener"
class="px-2 py-0.5 rounded text-[10px] font-medium ref-rfc hover:opacity-80 transition">
RFC {{ ref.id.replace('rfc', '') }}
RFC {{ ref.id | int }}
</a>
{% elif ref.type == 'draft' %}
{% if ref.id in known_drafts %}
<a href="/drafts/{{ ref.id }}"
class="px-2 py-0.5 rounded text-[10px] font-medium ref-draft hover:opacity-80 transition">
{{ ref.id }}
</a>
{% else %}
<a href="https://datatracker.ietf.org/doc/{{ ref.id }}/" target="_blank" rel="noopener"
class="px-2 py-0.5 rounded text-[10px] font-medium ref-draft hover:opacity-80 transition">
{{ ref.id }}
</a>
{% endif %}
{% elif ref.type == 'bcp' %}
<a href="https://www.rfc-editor.org/info/bcp{{ ref.id }}" target="_blank" rel="noopener"
class="px-2 py-0.5 rounded text-[10px] font-medium ref-other hover:opacity-80 transition">
BCP {{ ref.id }}
</a>
{% else %}
<span class="px-2 py-0.5 rounded text-[10px] font-medium ref-other">
{{ ref.type|upper }} {{ ref.id }}
</span>

View File

@@ -178,10 +178,10 @@
{% if categories %}
<div class="mt-4 pt-3 border-t border-slate-800/50">
<div class="flex flex-wrap gap-1.5">
<a href="/drafts?q={{ search }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
<a href="/drafts?q={{ search | urlencode }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
class="cat-pill {% if not current_cat %}cat-pill-active{% endif %}">All</a>
{% for cat, count in categories.items() %}
<a href="/drafts?cat={{ cat }}&q={{ search }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
<a href="/drafts?cat={{ cat }}&q={{ search | urlencode }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
class="cat-pill {% if current_cat == cat %}cat-pill-active{% endif %}">
{{ cat }} <span class="opacity-50">{{ count }}</span>
</a>
@@ -326,7 +326,7 @@
{% if result.pages > 1 %}
<nav class="flex items-center justify-center gap-1.5 mt-6">
{% if result.page > 1 %}
<a href="/drafts?page={{ result.page - 1 }}&q={{ search }}&cat={{ current_cat }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
<a href="/drafts?page={{ result.page - 1 }}&q={{ search | urlencode }}&cat={{ current_cat | urlencode }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
class="page-btn page-btn-inactive">
<svg class="w-4 h-4 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
Prev
@@ -337,7 +337,7 @@
{% set end_page = [result.pages, result.page + 2]|min %}
{% if start_page > 1 %}
<a href="/drafts?page=1&q={{ search }}&cat={{ current_cat }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
<a href="/drafts?page=1&q={{ search | urlencode }}&cat={{ current_cat | urlencode }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
class="page-btn page-btn-inactive">1</a>
{% if start_page > 2 %}<span class="text-slate-600 px-1">...</span>{% endif %}
{% endif %}
@@ -346,19 +346,19 @@
{% if p == result.page %}
<span class="page-btn page-btn-active">{{ p }}</span>
{% else %}
<a href="/drafts?page={{ p }}&q={{ search }}&cat={{ current_cat }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
<a href="/drafts?page={{ p }}&q={{ search | urlencode }}&cat={{ current_cat | urlencode }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
class="page-btn page-btn-inactive">{{ p }}</a>
{% endif %}
{% endfor %}
{% if end_page < result.pages %}
{% if end_page < result.pages - 1 %}<span class="text-slate-600 px-1">...</span>{% endif %}
<a href="/drafts?page={{ result.pages }}&q={{ search }}&cat={{ current_cat }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
<a href="/drafts?page={{ result.pages }}&q={{ search | urlencode }}&cat={{ current_cat | urlencode }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
class="page-btn page-btn-inactive">{{ result.pages }}</a>
{% endif %}
{% if result.page < result.pages %}
<a href="/drafts?page={{ result.page + 1 }}&q={{ search }}&cat={{ current_cat }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
<a href="/drafts?page={{ result.page + 1 }}&q={{ search | urlencode }}&cat={{ current_cat | urlencode }}&min_score={{ min_score }}&sort={{ sort }}&dir={{ sort_dir }}"
class="page-btn page-btn-inactive">
Next
<svg class="w-4 h-4 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>

View File

@@ -101,7 +101,7 @@
{% block extra_scripts %}
<script>
function downloadCurrentDraft() {
const text = {{ draft_text | tojson if draft_text else '""' }};
const text = {{ (draft_text or '') | tojson }};
const filename = {{ (draft_info.filename if draft_info else 'draft.txt') | tojson }};
if (!text) return;
const blob = new Blob([text], { type: 'text/plain' });

View File

@@ -57,7 +57,7 @@
<div class="divide-y divide-slate-800/50 max-h-[600px] overflow-y-auto" id="ideaList">
{% for idea in data.ideas %}
<div class="idea-item px-4 py-3 hover:bg-slate-800/50 transition"
data-search="{{ idea.title|lower }} {{ idea.description|lower }} {{ idea.draft_name|lower }}"
data-search="{{ (idea.title or '')|lower }} {{ (idea.description or '')|lower }} {{ (idea.draft_name or '')|lower }}"
data-type="{{ idea.type|default('other', true)|lower }}">
<div class="flex items-center gap-2 mb-1 flex-wrap">
<span class="text-sm font-medium text-slate-200">{{ idea.title }}</span>