Build and Push Docker Container / build-and-push (push) Successful in 2m37s
File cache: - Add my_modules/filecache with metadata, blob, validator and store layers - Cache convex.get_file via AsyncCache, TTL clamped to the file expiry - Skip caching for missing, incomplete or already expired records - Add a process-local, size-bounded LRU for immutable storage blobs - Build the cache stack from FILE_CACHE_* env vars in build_file_cache - Attach the cache to the app in my_modules/app/setup.py - Expose app-bound helpers in filecache/access.py for routes Serving: - Send strong ETag, Last-Modified and Cache-Control on served files - Answer If-None-Match and If-Modified-Since with 304 from metadata only - Avoid pulling the blob from Convex on a revalidation hit - Add a short Cloudflare-CDN-Cache-Control edge window for deleted files - Record cache_status in the wide event on a 304 - Invalidate cached metadata after file update and delete in both routes Text viewer: - Add my_modules/textview to render text/plain and text/markdown as HTML - Linkify http(s) URLs and trim trailing sentence punctuation - Resolve links pointing at this instance to the referenced file - Inline image previews, mark missing and expired targets - Cap resolution at MAX_LOOKUPS backend lookups per document - Serve the viewer only for real browser navigations, never for curl or CLI - Keep raw bytes for ?raw=1, downloads and Accept headers without text/html - Add templates/side/views/webpage/files/text_view.htm with a raw link Other: - Add tests for cache layers, serving, text rendering and the route - Bump multidict to 7.2.1 in uv.lock - Update the my_helpers submodule pointer
58 lines
2.1 KiB
HTML
58 lines
2.1 KiB
HTML
{% extends "base.htm" %}
|
|
|
|
{% block title %}NanoShare - {{ file_name }}{% endblock %}
|
|
|
|
{% block meta %}
|
|
<meta name="robots" content="noindex, nofollow" />
|
|
{% endblock %}
|
|
|
|
{% block head %}
|
|
<style>
|
|
.text-view-page { padding: clamp(16px, 3vw, 28px); }
|
|
.text-view-head { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; justify-content: space-between; }
|
|
.text-view-body {
|
|
margin: 16px 0 0;
|
|
padding: clamp(12px, 2vw, 18px);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
background: rgba(0, 0, 0, .16);
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
font-size: .95rem;
|
|
line-height: 1.6;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
}
|
|
.text-view-body a { color: var(--accent, #6cf); }
|
|
.text-view-ref { display: inline-flex; align-items: center; gap: 6px; vertical-align: middle; }
|
|
.text-view-ref img {
|
|
max-height: 120px;
|
|
max-width: 180px;
|
|
border-radius: 6px;
|
|
border: 1px solid var(--border);
|
|
vertical-align: middle;
|
|
}
|
|
.text-view-state { color: var(--muted); font-size: .85rem; }
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<main class="text-view-page">
|
|
<section class="card" style="padding: clamp(18px, 2.6vw, 28px);">
|
|
<div class="text-view-head">
|
|
<h1 class="page-title" style="margin:0;">{{ file_name }}</h1>
|
|
<a class="btn btn-ghost" href="{{ raw_url }}">View raw</a>
|
|
</div>
|
|
|
|
<div class="text-view-body">{% for segment in segments -%}
|
|
{%- if segment.kind == 'text' -%}
|
|
{{ segment.text }}
|
|
{%- elif segment.kind == 'file' -%}
|
|
<span class="text-view-ref"><a href="{{ segment.url }}" rel="noopener noreferrer">{{ segment.label }}</a>{% if segment.media == 'image' %} <a href="{{ segment.url }}" rel="noopener noreferrer"><img src="{{ segment.url }}" alt="{{ segment.label }}" loading="lazy"></a>{% endif %}</span>
|
|
{%- else -%}
|
|
<a href="{{ segment.url }}" rel="noopener noreferrer" target="_blank">{{ segment.label or segment.url }}</a>{% if segment.state == 'expired' %} <span class="text-view-state">(expired)</span>{% endif %}
|
|
{%- endif -%}
|
|
{%- endfor %}</div>
|
|
</section>
|
|
</main>
|
|
{% endblock %}
|