2026-05-12 15:07:34 +02:00
{% extends "base.html" %}
feat: add leaderboard router, gamification backfill, and shared content creation helpers
Introduce a new `/leaderboard` endpoint ranking top 50 members by total stars, wire it into the app router and documentation. Implement `_backfill_gamification()` in `database.py` to compute XP/levels for existing accounts from prior posts, comments, votes, and follows. Extract `is_owner()`, `create_content_item()`, and `detail_context()` into `content.py` to centralize content creation, reward awarding, mention notifications, and attachment linking. Update comment creation/deletion in `comments.py` to use `award_rewards()` with `XP_COMMENT` and the new `is_owner()` helper. Add shared template partials documentation for vote bars, star buttons, post headers, and topic selectors to `AGENTS.md`.
2026-05-30 20:16:39 +02:00
{% from "_macros.html" import modal %}
2026-05-12 15:07:34 +02:00
{% block extra_head %}
2026-06-14 02:16:22 +02:00
< link rel = "stylesheet" href = "{{ static_url('/static/css/gists.css') }}" >
< link rel = "stylesheet" href = "{{ static_url('/static/css/post.css') }}" >
< link rel = "stylesheet" href = "{{ static_url('/static/vendor/codemirror/codemirror.min.css') }}" >
< link rel = "stylesheet" href = "{{ static_url('/static/vendor/codemirror/theme/monokai.min.css') }}" >
2026-05-12 15:07:34 +02:00
{% endblock %}
{% block content %}
< div class = "gist-detail-page" >
< a href = "/gists" class = "back-link" > ← Back to Gists< / a >
< article class = "gist-detail" >
< div class = "gist-detail-header" >
2026-06-19 00:09:34 +02:00
< h1 class = "gist-detail-title" > {{ render_title(gist['title']) }}< / h1 >
2026-05-13 21:17:57 +02:00
< span class = "gist-language-badge" > 📝 {{ language_name(gist['language']) }}< / span >
2026-05-12 15:07:34 +02:00
< / div >
< div class = "gist-detail-author" >
{% set _size = 32 %}{% set _size_class = "sm" %}{% set _user = author %}{% include "_avatar_link.html" %}
< div >
2026-06-02 23:17:51 +02:00
{% set _user = author %}{% set _class = none %}{% include "_user_link.html" %}
feat: add alt text extraction from URLs for images and improve CSS variable usage for progress bars
Add `_alt_from_url` helper in Python rendering and `altFromUrl` in JS ContentRenderer to generate descriptive alt attributes from image filenames, replacing empty alt strings. Update image embed templates and Avatar component to use derived alt text. Migrate inline `style.width` assignments to CSS custom properties (`--poll-pct`, `--hud-xp`, `--quest-pct`, `--bar-pct`, `--quota-pct`) across PollManager, GameFarm, and profile/quest CSS for better maintainability. Introduce new admin, docs, projects, services, and base CSS utility classes. Add "Code Farm" docs page entry.
2026-06-23 01:13:48 +02:00
< span class = "meta-muted" > · Level {{ author.get('level', 1) if author else 1 }}< / span >
< span class = "meta-muted meta-muted-spaced" > · {{ dt_ago(gist.created_at) if gist.get('created_at') else time_ago }}< / span >
2026-05-12 15:07:34 +02:00
< / div >
< / div >
{% if gist.get('description') %}
2026-06-19 00:09:34 +02:00
< div class = "gist-detail-desc rendered-content" > {{ render_content(gist['description']) }}< / div >
2026-05-12 15:07:34 +02:00
{% endif %}
{% if attachments %}
{% include "_attachment_display.html" %}
{% endif %}
< div class = "gist-code-block" >
< div class = "gist-code-header" >
2026-05-13 21:17:57 +02:00
< span class = "gist-language-name" > {{ language_name(gist['language']) }}< / span >
2026-05-12 15:07:34 +02:00
< button type = "button" class = "gist-copy-btn" data-copy = "gist-code-content" > Copy< / button >
< / div >
< pre > < code id = "gist-code-content" class = "language-{{ gist['language'] }}" > {{ gist['source_code'] }}< / code > < / pre >
< / div >
< div class = "gist-detail-actions" >
2026-06-16 00:44:12 +02:00
< button type = "button" class = "gist-star-btn" data-share = "/gists/{{ gist['slug'] or gist['uid'] }}" > < span class = "icon" > 🔗 < / span > < span class = "label" > Share< / span > < / button >
2026-05-12 15:07:34 +02:00
{% if user %}
feat: add leaderboard router, gamification backfill, and shared content creation helpers
Introduce a new `/leaderboard` endpoint ranking top 50 members by total stars, wire it into the app router and documentation. Implement `_backfill_gamification()` in `database.py` to compute XP/levels for existing accounts from prior posts, comments, votes, and follows. Extract `is_owner()`, `create_content_item()`, and `detail_context()` into `content.py` to centralize content creation, reward awarding, mention notifications, and attachment linking. Update comment creation/deletion in `comments.py` to use `award_rewards()` with `XP_COMMENT` and the new `is_owner()` helper. Add shared template partials documentation for vote bars, star buttons, post headers, and topic selectors to `AGENTS.md`.
2026-05-30 20:16:39 +02:00
{% set _type = "gist" %}{% set _uid = gist['uid'] %}{% set _my_vote = my_vote %}{% set _count = star_count %}{% set _btn_class = "gist-star-btn" %}{% include "_star_vote.html" %}
2026-05-12 15:07:34 +02:00
{% endif %}
2026-06-06 16:31:42 +02:00
{% set _type = "gist" %}{% set _uid = gist['uid'] %}{% set _bookmarked = bookmarked %}{% include "_bookmark_button.html" %}
2026-05-12 15:07:34 +02:00
{% if is_owner %}
2026-06-16 00:44:12 +02:00
< button type = "button" class = "gist-star-btn" data-modal = "edit-gist-modal" > < span class = "icon" > ✏ ️ < / span > < span class = "label" > Edit< / span > < / button >
feat: add admin/internal database API with CRUD, read-only query, and natural-language SQL endpoints
Add a new `/dbapi` router package providing a generic database API over `dataset`, restricted to admin sessions, admin API keys, and the internal gateway key. Includes:
- `tables.py`: list all tables and inspect table schemas
- `crud.py`: full CRUD operations (GET, POST, PATCH, DELETE) with soft-delete awareness, born-live inserts, `?include_deleted`, `.../restore`, and `?hard=true` purge
- `query.py`: validated read-only SELECT execution via sqlglot parsing, classification, and EXPLAIN dry-run; async query jobs with WebSocket streaming via `DbApiJobService`
- `nl.py`: natural-language-to-SQL conversion using the platform AI gateway with re-prompting until validation passes
Also register `DbApiJobService` and `PubSubService` in the service manager, add `DBAPI_DIR` to config data paths, and force cleartext `http://` connections to HTTP/1.1 in `curl_transport` to fix large request failures against uvicorn's HTTP/1.1-only internal gateway.
2026-06-15 01:00:30 +02:00
{% endif %}
{% if is_owner or is_admin(user) %}
feat: add alt text extraction from URLs for images and improve CSS variable usage for progress bars
Add `_alt_from_url` helper in Python rendering and `altFromUrl` in JS ContentRenderer to generate descriptive alt attributes from image filenames, replacing empty alt strings. Update image embed templates and Avatar component to use derived alt text. Migrate inline `style.width` assignments to CSS custom properties (`--poll-pct`, `--hud-xp`, `--quest-pct`, `--bar-pct`, `--quota-pct`) across PollManager, GameFarm, and profile/quest CSS for better maintainability. Introduce new admin, docs, projects, services, and base CSS utility classes. Add "Code Farm" docs page entry.
2026-06-23 01:13:48 +02:00
< form method = "POST" action = "/gists/delete/{{ gist['slug'] or gist['uid'] }}" class = "inline-form" >
2026-06-16 00:44:12 +02:00
< button type = "submit" class = "gist-star-btn" data-confirm = "Delete this gist?" > < span class = "icon" > 🗑 ️ < / span > < span class = "label" > Delete< / span > < / button >
2026-05-12 15:07:34 +02:00
< / form >
{% endif %}
2026-06-06 16:31:42 +02:00
{% set _type = "gist" %}{% set _uid = gist['uid'] %}{% set _reactions = reactions %}{% include "_reaction_bar.html" %}
2026-05-12 15:07:34 +02:00
< / div >
< / article >
2026-05-13 21:17:57 +02:00
{% if is_owner %}
feat: add leaderboard router, gamification backfill, and shared content creation helpers
Introduce a new `/leaderboard` endpoint ranking top 50 members by total stars, wire it into the app router and documentation. Implement `_backfill_gamification()` in `database.py` to compute XP/levels for existing accounts from prior posts, comments, votes, and follows. Extract `is_owner()`, `create_content_item()`, and `detail_context()` into `content.py` to centralize content creation, reward awarding, mention notifications, and attachment linking. Update comment creation/deletion in `comments.py` to use `award_rewards()` with `XP_COMMENT` and the new `is_owner()` helper. Add shared template partials documentation for vote bars, star buttons, post headers, and topic selectors to `AGENTS.md`.
2026-05-30 20:16:39 +02:00
{% call modal('edit-gist-modal', 'Edit Gist', wide=true) %}
2026-05-13 21:17:57 +02:00
< form method = "POST" action = "/gists/edit/{{ gist['slug'] or gist['uid'] }}" >
< div class = "auth-field auth-field-gap" >
< label for = "edit-gist-title" > Title< / label >
< input type = "text" id = "edit-gist-title" name = "title" required maxlength = "200" value = "{{ gist['title'] }}" >
< / div >
< div class = "auth-field auth-field-gap" >
< label for = "edit-gist-description" > Description (optional)< / label >
< textarea id = "edit-gist-description" name = "description" maxlength = "5000" class = "min-h-80" data-mention > {{ gist.get('description', '') }}< / textarea >
< / div >
< div class = "auth-field auth-field-gap" >
< label for = "edit-gist-language" > Language< / label >
< select id = "edit-gist-language" name = "language" >
{% for code, name in languages %}
< option value = "{{ code }}" { % if code = = gist [ ' language ' ] % } selected { % endif % } > {{ name }}< / option >
{% endfor %}
< / select >
< / div >
< div class = "auth-field auth-field-gap-last" >
< label for = "edit-gist-source-editor" > Source Code< / label >
2026-05-16 03:10:55 +02:00
< textarea id = "edit-gist-source-editor" name = "source_code" class = "hidden-textarea" data-language = "{{ gist['language'] }}" > {{ gist['source_code'] }}< / textarea >
2026-05-13 21:17:57 +02:00
< / div >
{% include "_attachment_form.html" %}
< div class = "modal-footer" >
< button type = "button" class = "modal-close btn btn-secondary" > Cancel< / button >
< button type = "submit" class = "btn btn-primary" > < span class = "icon" > 💾 < / span > Save Changes< / button >
< / div >
< / form >
feat: add leaderboard router, gamification backfill, and shared content creation helpers
Introduce a new `/leaderboard` endpoint ranking top 50 members by total stars, wire it into the app router and documentation. Implement `_backfill_gamification()` in `database.py` to compute XP/levels for existing accounts from prior posts, comments, votes, and follows. Extract `is_owner()`, `create_content_item()`, and `detail_context()` into `content.py` to centralize content creation, reward awarding, mention notifications, and attachment linking. Update comment creation/deletion in `comments.py` to use `award_rewards()` with `XP_COMMENT` and the new `is_owner()` helper. Add shared template partials documentation for vote bars, star buttons, post headers, and topic selectors to `AGENTS.md`.
2026-05-30 20:16:39 +02:00
{% endcall %}
2026-05-13 21:17:57 +02:00
{% endif %}
2026-05-12 15:07:34 +02:00
{% with target_uid=gist['uid'], target_type="gist" %}
{% include "_comment_section.html" %}
{% endwith %}
< / div >
{% endblock %}
2026-05-13 21:17:57 +02:00
{% block extra_js %}
2026-06-14 02:16:22 +02:00
< script defer src = "{{ static_url('/static/vendor/codemirror/codemirror.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/python/python.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/javascript/javascript.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/clike/clike.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/htmlmixed/htmlmixed.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/xml/xml.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/css/css.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/go/go.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/rust/rust.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/sql/sql.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/shell/shell.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/yaml/yaml.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/markdown/markdown.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/swift/swift.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/php/php.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/ruby/ruby.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/haskell/haskell.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/lua/lua.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/perl/perl.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/r/r.min.js') }}" > < / script >
< script defer src = "{{ static_url('/static/vendor/codemirror/mode/dart/dart.min.js') }}" > < / script >
< script type = "module" src = "{{ static_url('/static/js/GistEditor.js') }}" > < / script >
2026-05-13 21:17:57 +02:00
< script type = "module" >
const editModal = document.getElementById("edit-gist-modal");
if (editModal) {
const editGistEditor = new window.GistEditor("edit-gist-source-editor", "edit-gist-language");
const observer = new MutationObserver(() => {
if (editModal.classList.contains("visible")) {
editGistEditor.refresh();
}
});
observer.observe(editModal, { attributes: true, attributeFilter: ["class"] });
}
< / script >
{% endblock %}