added custom template editor, cleaned up UI

- Replace CSS-only customization with full HTML template editing
- Users edit the entire page wrapper with {{content}} placeholder
- Add /style?reset escape hatch to recover from broken templates
- Move nav links to template, remove redundant nav from search page
- Delete remote pages when unsubscribing from an instance
This commit is contained in:
lichenblankie 2026-03-26 09:04:23 -07:00
parent 6f88b7cf57
commit 02450b0865
3 changed files with 44 additions and 36 deletions

View file

@ -2,15 +2,15 @@ import json
from datetime import datetime
from db import get_db, get_setting, set_setting, get_site_name, index_url, clean_url
from templates import esc, snippet, wrap_page
from templates import esc, snippet, wrap_page, DEFAULT_TEMPLATE
from rns_client import fetch_remote_sites
def _respond(body_html, status=200):
def _respond(body_html, status=200, use_default=False):
return {
"status": status,
"content_type": "text/html; charset=utf-8",
"body": wrap_page(body_html),
"body": wrap_page(body_html, use_default=use_default),
"headers": {},
}
@ -186,19 +186,13 @@ def handle_search(query):
if q and remote_rows:
sub_count = f" + {len(remote_rows)} from subscriptions"
return _respond(
f'<h1><a href="/">{esc(name)}</a></h1>'
f'<form method="get" action="/">'
f'<input name="q" value="{esc(q)}" placeholder="search your index" size="40">'
f' <button type="submit">search</button>'
f'</form>'
f'<p>{count} page(s) indexed.'
f' <a href="/add">+ add url</a>'
f' | <a href="/pages">browse</a>'
f' | <a href="/tags">tags</a>'
f' | <a href="/subscriptions">subscriptions</a>'
f' | <a href="/style">customize</a>'
f' | <a href="/about">about</a></p>'
f'<hr>{result_html}{trusted_html}{remote_html}'
f'<p class="meta">{count} pages indexed'
f' · <a href="/add">+ add url</a></p>'
f'{result_html}{trusted_html}{remote_html}'
)
@ -366,8 +360,11 @@ def handle_import_submit(body):
return handle_import_form(f"Imported {imported} page(s). {errors} error(s).")
def handle_style_form(msg=""):
css = get_setting("custom_css")
def handle_style_form(msg="", query=None):
if query and "reset" in query:
set_setting("custom_template", "")
msg = "Template reset to default."
template = get_setting("custom_template") or DEFAULT_TEMPLATE
name = get_site_name()
sharing = get_setting("sharing_enabled", "0")
checked = " checked" if sharing == "1" else ""
@ -379,35 +376,26 @@ def handle_style_form(msg=""):
f"<h2>sharing</h2>"
f'<label><input type="checkbox" name="sharing_enabled" value="1"{checked}>'
f" share your site list publicly at /api/sites</label><br><br>"
f"<h2>custom css</h2>"
f"<p>Some classes you can target:</p>"
f"<pre>"
f"body - page background, font\n"
f"h1 - page titles\n"
f"input, button - search bar\n"
f"a - links\n"
f".result - each search result\n"
f".note - your notes on results\n"
f".trusted - trusted sites dropdown\n"
f"small - url text\n"
f"ul, li - browse page list"
f"</pre>"
f'<textarea name="css" rows="16" cols="60">{esc(css)}</textarea><br><br>'
f"<h2>custom html</h2>"
f"<p>Edit the full page template. Use <code>{esc('{{content}}')}</code> "
f"where page content should appear.</p>"
f'<textarea name="template" rows="20" cols="60">{esc(template)}</textarea><br><br>'
f'<button type="submit">save</button>'
f"</form>"
f"<h2>bookmarklet</h2>"
f"<p>Drag this link to your bookmarks bar. Click it on any page to index it instantly.</p>"
f'<p><a href="javascript:void(fetch(\'http://localhost:8080/bookmark?url=\'+encodeURIComponent(location.href)).then(r=>r.text()).then(t=>alert(t)).catch(()=>alert(\'tinyweb not running\')))">+ save to {esc(name)}</a></p>'
f"<p>{msg}</p>"
f'<a href="/">back</a>'
f'<a href="/">back</a>',
use_default=True,
)
def handle_style_submit(body):
css = body.get("css", [""])[0]
template = body.get("template", [""])[0]
name = body.get("site_name", ["tinyweb"])[0].strip()
sharing = "1" if body.get("sharing_enabled") else "0"
set_setting("custom_css", css)
set_setting("custom_template", template if template.strip() != DEFAULT_TEMPLATE.strip() else "")
set_setting("site_name", name or "tinyweb")
set_setting("sharing_enabled", sharing)
return handle_style_form("Saved.")
@ -755,6 +743,7 @@ def handle_subscription_autosync(sub_id):
def handle_subscription_delete(sub_id):
db = get_db()
db.execute("DELETE FROM remote_pages WHERE subscription_id = ?", (sub_id,))
db.execute("DELETE FROM subscriptions WHERE id = ?", (sub_id,))
db.commit()
db.close()
@ -826,7 +815,7 @@ def dispatch_request(data):
elif path == "/bookmark":
return handle_bookmark(query)
elif path == "/style":
return handle_style_form()
return handle_style_form(query=query)
elif path == "/about":
return handle_about()
elif path == "/export":