From 80a1d44deed14e01e93c5dfe4514dc2dbcd9e56c Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 30 Mar 2026 22:36:58 +0000 Subject: [PATCH 01/12] Add reticulum destination hash option to add URL page --- db.py | 17 +++++++++++------ handlers.py | 46 +++++++++++++++++++++++++++++++++------------- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/db.py b/db.py index a0a3580..4943855 100644 --- a/db.py +++ b/db.py @@ -131,7 +131,8 @@ def init_db(): " title TEXT," " body TEXT," " note TEXT DEFAULT ''," - " last_modified TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%S','now'))" + " last_modified TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%S','now'))," + " reticulum_dest TEXT DEFAULT ''" ")" ) db.execute( @@ -247,6 +248,11 @@ def init_db(): db.execute("ALTER TABLE pages ADD COLUMN summary TEXT DEFAULT ''") db.commit() + # Migrate pages: add reticulum_dest column if missing + if "reticulum_dest" not in page_cols: + db.execute("ALTER TABLE pages ADD COLUMN reticulum_dest TEXT DEFAULT ''") + db.commit() + # Chunks table for semantic search embeddings db.execute( "CREATE TABLE IF NOT EXISTS chunks (" @@ -359,19 +365,18 @@ def fetch_page(url): -def index_url(url, note=""): +def index_url(url, note="", reticulum_dest=""): url = clean_url(url) title, body, links, meta_desc = fetch_page(url) - # Use meta description if available and meaningful, otherwise generate from body summary = meta_desc if meta_desc and len(meta_desc) > 20 else "" db = get_db() try: now = __import__("datetime").datetime.now().strftime("%Y-%m-%dT%H:%M:%S") db.execute( - "INSERT INTO pages (url, title, body, note, last_modified, summary) VALUES (?, ?, ?, ?, ?, ?) " + "INSERT INTO pages (url, title, body, note, last_modified, summary, reticulum_dest) VALUES (?, ?, ?, ?, ?, ?, ?) " "ON CONFLICT(url) DO UPDATE SET title=excluded.title, body=excluded.body, " - "note=excluded.note, last_modified=excluded.last_modified, summary=excluded.summary", - (url, title, body, note, now, summary), + "note=excluded.note, last_modified=excluded.last_modified, summary=excluded.summary, reticulum_dest=excluded.reticulum_dest", + (url, title, body, note, now, summary, reticulum_dest), ) page_id = db.execute("SELECT id FROM pages WHERE url = ?", (url,)).fetchone()[0] db.execute("DELETE FROM links WHERE page_id = ?", (page_id,)) diff --git a/handlers.py b/handlers.py index 5903d0b..a5e66d4 100644 --- a/handlers.py +++ b/handlers.py @@ -341,9 +341,12 @@ def handle_search(query): def handle_add_form(msg=""): return _respond( f"

add url

" + f"

Add a site via URL or Reticulum destination hash

" f'
' f'{_csrf_field()}' - f'

' + f'
' + f'or
' + f'

' f'

' f'

' f'' @@ -357,15 +360,25 @@ def handle_add_submit(body): url = clean_url(body.get("url", [""])[0].strip()) note = body.get("note", [""])[0].strip() tags = body.get("tags", [""])[0].strip() + reticulum_dest = body.get("reticulum_dest", [""])[0].strip().replace("<", "").replace(">", "") - if not url: - return handle_add_form("URL is required.") - if not url.startswith(("http://", "https://")): + if reticulum_dest and url: + return handle_add_form("Please provide either a URL or a Reticulum destination hash, not both.") + + if not url and not reticulum_dest: + return handle_add_form("URL or Reticulum destination hash is required.") + + if reticulum_dest and (len(reticulum_dest) != 32 or not all(c in "0123456789abcdefABCDEF" for c in reticulum_dest)): + return handle_add_form("Invalid reticulum destination hash. Must be 32 hex characters.") + + if url and not url.startswith(("http://", "https://")): return handle_add_form("URL must start with http:// or https://") - # Try auto-index first + if reticulum_dest and not url: + url = f"reticulum:{reticulum_dest}" + try: - title = index_url(url, note) + title = index_url(url, note, reticulum_dest) if tags: db = get_db() try: @@ -375,7 +388,9 @@ def handle_add_submit(body): db.commit() finally: return_db(db) - return handle_add_form(f'Indexed: {esc(title)}') + + display_url = url if url.startswith("http") else reticulum_dest + return handle_add_form(f'Indexed: {esc(display_url)}') except ValueError as e: return handle_add_form(f"Error: {esc(str(e))}") @@ -394,6 +409,7 @@ def handle_add_submit(body): f'' f'' f'' + f'' f'
' f'

' f'
' @@ -409,11 +425,16 @@ def handle_add_manual_submit(body): url = clean_url(body.get("url", [""])[0].strip()) note = body.get("note", [""])[0].strip() tags = body.get("tags", [""])[0].strip() + reticulum_dest = body.get("reticulum_dest", [""])[0].strip() manual_title = body.get("manual_title", [""])[0].strip() manual_desc = body.get("manual_description", [""])[0].strip() - if not url: - return handle_add_form("URL is required.") + if not url and not reticulum_dest: + return handle_add_form("URL or Reticulum destination hash is required.") + + if not url and reticulum_dest: + url = f"reticulum:{reticulum_dest}" + if not manual_title or not manual_desc: return handle_add_form("Title and description are required for manual entry.") @@ -421,12 +442,11 @@ def handle_add_manual_submit(body): try: now = __import__("datetime").datetime.now().strftime("%Y-%m-%dT%H:%M:%S") - # Insert the page db.execute( - "INSERT INTO pages (url, title, body, note, last_modified, summary) VALUES (?, ?, ?, ?, ?, ?) " + "INSERT INTO pages (url, title, body, note, last_modified, summary, reticulum_dest) VALUES (?, ?, ?, ?, ?, ?, ?) " "ON CONFLICT(url) DO UPDATE SET title=excluded.title, body=excluded.body, " - "note=excluded.note, last_modified=excluded.last_modified, summary=excluded.summary", - (url, manual_title, manual_desc, note, now, manual_desc[:200]), + "note=excluded.note, last_modified=excluded.last_modified, summary=excluded.summary, reticulum_dest=excluded.reticulum_dest", + (url, manual_title, manual_desc, note, now, manual_desc[:200], reticulum_dest), ) # Get the page ID From a3429409ebf222d58b475ec2500a1aefafd345f3 Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 30 Mar 2026 22:48:45 +0000 Subject: [PATCH 02/12] Add dropdown to switch between add site and subscribe in same input box --- handlers.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/handlers.py b/handlers.py index a5e66d4..0d6135b 100644 --- a/handlers.py +++ b/handlers.py @@ -338,12 +338,29 @@ def handle_search(query): ) -def handle_add_form(msg=""): +def handle_add_form(msg="", action_type="index"): + if action_type == "subscribe": + return _respond( + f"

subscribe

" + f"

Subscribe to a friend's TinyWeb instance to sync their index

" + f'' + f'{_csrf_field()}' + f'

' + f'' + f"" + f"

or add a single site

" + f"

{msg}

" + f'back' + ) return _respond( f"

add url

" f"

Add a site via URL or Reticulum destination hash

" f'
' f'{_csrf_field()}' + f'

' f'
' f'or
' f'

' @@ -351,6 +368,7 @@ def handle_add_form(msg=""): f'

' f'' f"
" + f"

or manage subscriptions

" f"

{msg}

" f'back' ) @@ -901,6 +919,7 @@ def handle_subscriptions(msg=""): f' ' f'' f'' + f'

or subscribe to an instance

' f'

{msg}

' f'
{listing}' f'
back' @@ -1266,7 +1285,8 @@ def _dispatch_inner(data): if path == "/": return handle_search(query) elif path == "/add": - return handle_add_form() + action_type = query.get("type", ["index"])[0] + return handle_add_form(action_type=action_type if action_type == "subscribe" else "index") elif path == "/pages": return handle_pages(query) elif path.startswith("/edit/"): From d6616f69d536103099d9f1ff36a88f522783d2bb Mon Sep 17 00:00:00 2001 From: lichenblankie Date: Mon, 30 Mar 2026 22:49:57 +0000 Subject: [PATCH 03/12] Update handler --- handlers.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/handlers.py b/handlers.py index a5e66d4..35e89dc 100644 --- a/handlers.py +++ b/handlers.py @@ -475,6 +475,8 @@ def handle_add_manual_submit(body): def handle_pages(query=None): + msg = query.get("msg", [""])[0] if query else "" + msg_html = f'

{esc(msg)}

' if msg else "" page = _paginate(query or {}) offset = (page - 1) * BROWSE_PER_PAGE db = get_db() @@ -502,6 +504,7 @@ def handle_pages(query=None): return_db(db) return _respond( f"

indexed pages ({total})

" + f"{msg_html}" f"
    {items}
" f'{_page_nav(page, total, "/pages", BROWSE_PER_PAGE)}' f'

export | import

' @@ -512,20 +515,27 @@ def handle_pages(query=None): def handle_edit_form(page_id, msg=""): db = get_db() try: - row = db.execute("SELECT id, url, title, note FROM pages WHERE id = ?", (page_id,)).fetchone() + row = db.execute("SELECT id, url, title, body, note, summary FROM pages WHERE id = ?", (page_id,)).fetchone() if not row: return _error(404) tags = ", ".join(_get_page_tags(page_id, db)) finally: return_db(db) + return _respond( f"

edit page

" f"

{esc(row['title'])}
" f"{esc(row['url'])}

" f'
' f'{_csrf_field()}' - f'

' - f'

' + f'
' + f'

' + f'
' + f'

' + f'
' + f'

' + f'
' + f'

' f'' f"
" f"

{msg}

" @@ -534,15 +544,25 @@ def handle_edit_form(page_id, msg=""): def handle_edit_submit(page_id, body): + title = body.get("title", [""])[0].strip() + summary = body.get("summary", [""])[0].strip() note = body.get("note", [""])[0].strip() tags = body.get("tags", [""])[0].strip() + db = get_db() try: - db.execute("UPDATE pages SET note = ? WHERE id = ?", (note, page_id)) + db.execute( + "UPDATE pages SET title = ?, summary = ?, note = ? WHERE id = ?", + (title, summary, note, page_id) + ) + _set_page_tags(page_id, tags, db) + db.commit() + finally: return_db(db) + return _redirect("/pages") From 77956621544f700612fcb254acc5ddc36879b693 Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 30 Mar 2026 22:54:29 +0000 Subject: [PATCH 04/12] Add radio toggle for URL vs Reticulum hash input in add page --- handlers.py | 73 ++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/handlers.py b/handlers.py index 0d6135b..f334898 100644 --- a/handlers.py +++ b/handlers.py @@ -354,49 +354,54 @@ def handle_add_form(msg="", action_type="index"): ) return _respond( f"

add url

" - f"

Add a site via URL or Reticulum destination hash

" + f"

Add a site to your index

" f'
' f'{_csrf_field()}' - f'

' - f'
' - f'or
' - f'

' + f'' + f'' + f'' + f'' + f'

' + f'
' + f'

' f'

' f'

' f'' f"
" - f"

or manage subscriptions

" f"

{msg}

" f'back' + f'' ) def handle_add_submit(body): - url = clean_url(body.get("url", [""])[0].strip()) + input_type = body.get("input_type", ["url"])[0] + url = body.get("url", [""])[0].strip() + reticulum_dest = body.get("reticulum_dest", [""])[0].strip().replace("<", "").replace(">", "") note = body.get("note", [""])[0].strip() tags = body.get("tags", [""])[0].strip() - reticulum_dest = body.get("reticulum_dest", [""])[0].strip().replace("<", "").replace(">", "") - if reticulum_dest and url: - return handle_add_form("Please provide either a URL or a Reticulum destination hash, not both.") - - if not url and not reticulum_dest: - return handle_add_form("URL or Reticulum destination hash is required.") - - if reticulum_dest and (len(reticulum_dest) != 32 or not all(c in "0123456789abcdefABCDEF" for c in reticulum_dest)): - return handle_add_form("Invalid reticulum destination hash. Must be 32 hex characters.") - - if url and not url.startswith(("http://", "https://")): - return handle_add_form("URL must start with http:// or https://") - - if reticulum_dest and not url: + if input_type == "url": + if not url: + return handle_add_form("URL is required.") + url = clean_url(url) + if not url.startswith(("http://", "https://")): + return handle_add_form("URL must start with http:// or https://") + else: + if not reticulum_dest: + return handle_add_form("Reticulum destination hash is required.") + if len(reticulum_dest) != 32 or not all(c in "0123456789abcdefABCDEF" for c in reticulum_dest): + return handle_add_form("Invalid reticulum destination hash. Must be 32 hex characters.") url = f"reticulum:{reticulum_dest}" try: - title = index_url(url, note, reticulum_dest) + title = index_url(url, note, reticulum_dest if reticulum_dest else "") if tags: db = get_db() try: @@ -407,8 +412,7 @@ def handle_add_submit(body): finally: return_db(db) - display_url = url if url.startswith("http") else reticulum_dest - return handle_add_form(f'Indexed: {esc(display_url)}') + return handle_add_form(f'Indexed: {esc(url)}') except ValueError as e: return handle_add_form(f"Error: {esc(str(e))}") @@ -427,7 +431,6 @@ def handle_add_submit(body): f'' f'' f'' - f'' f'
' f'

' f'
' @@ -443,15 +446,11 @@ def handle_add_manual_submit(body): url = clean_url(body.get("url", [""])[0].strip()) note = body.get("note", [""])[0].strip() tags = body.get("tags", [""])[0].strip() - reticulum_dest = body.get("reticulum_dest", [""])[0].strip() manual_title = body.get("manual_title", [""])[0].strip() manual_desc = body.get("manual_description", [""])[0].strip() - if not url and not reticulum_dest: - return handle_add_form("URL or Reticulum destination hash is required.") - - if not url and reticulum_dest: - url = f"reticulum:{reticulum_dest}" + if not url: + return handle_add_form("URL is required.") if not manual_title or not manual_desc: return handle_add_form("Title and description are required for manual entry.") @@ -461,10 +460,10 @@ def handle_add_manual_submit(body): now = __import__("datetime").datetime.now().strftime("%Y-%m-%dT%H:%M:%S") db.execute( - "INSERT INTO pages (url, title, body, note, last_modified, summary, reticulum_dest) VALUES (?, ?, ?, ?, ?, ?, ?) " + "INSERT INTO pages (url, title, body, note, last_modified, summary) VALUES (?, ?, ?, ?, ?, ?) " "ON CONFLICT(url) DO UPDATE SET title=excluded.title, body=excluded.body, " - "note=excluded.note, last_modified=excluded.last_modified, summary=excluded.summary, reticulum_dest=excluded.reticulum_dest", - (url, manual_title, manual_desc, note, now, manual_desc[:200], reticulum_dest), + "note=excluded.note, last_modified=excluded.last_modified, summary=excluded.summary", + (url, manual_title, manual_desc, note, now, manual_desc[:200]), ) # Get the page ID From ea8f256882f85c1a9910889e7fda6fef0047d16a Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 30 Mar 2026 23:01:23 +0000 Subject: [PATCH 05/12] Fix gap in add form between URL and note fields --- handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers.py b/handlers.py index bc39743..1a53b8f 100644 --- a/handlers.py +++ b/handlers.py @@ -363,7 +363,7 @@ def handle_add_form(msg="", action_type="index"): f'' f'

' f'
' - f'

' + f'
' f'

' f'

' f'' From fb4d4dbaec6480ca16fba6cc9a9f28598881f788 Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 30 Mar 2026 23:03:00 +0000 Subject: [PATCH 06/12] Fix inconsistent spacing in add form when toggling input type --- handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers.py b/handlers.py index 1a53b8f..e5b0143 100644 --- a/handlers.py +++ b/handlers.py @@ -364,7 +364,7 @@ def handle_add_form(msg="", action_type="index"): f'

' f'
' f'
' - f'

' + f'
' f'

' f'' f"" From 756493e28686205a14f5d077974b6ae74454de58 Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 30 Mar 2026 23:03:57 +0000 Subject: [PATCH 07/12] Fix toggle gap using CSS margin for consistent spacing --- handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/handlers.py b/handlers.py index e5b0143..cb180f8 100644 --- a/handlers.py +++ b/handlers.py @@ -364,8 +364,8 @@ def handle_add_form(msg="", action_type="index"): f'

' f'
' f'
' - f'
' - f'

' + f'
' + f'

' f'' f"" f"

{msg}

" From 3bebb5734b04123e0a30c6236d43e9b5f389d5c6 Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 30 Mar 2026 23:04:52 +0000 Subject: [PATCH 08/12] Remove CSS, use consistent br spacing in add form --- handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/handlers.py b/handlers.py index cb180f8..e5b0143 100644 --- a/handlers.py +++ b/handlers.py @@ -364,8 +364,8 @@ def handle_add_form(msg="", action_type="index"): f'

' f'
' f'
' - f'
' - f'

' + f'
' + f'

' f'' f"" f"

{msg}

" From da95e580f4a10395dd9479e930401358efc80725 Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 30 Mar 2026 23:06:12 +0000 Subject: [PATCH 09/12] Add extra line break between note and tags for better spacing --- handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers.py b/handlers.py index e5b0143..8f62d7d 100644 --- a/handlers.py +++ b/handlers.py @@ -365,7 +365,7 @@ def handle_add_form(msg="", action_type="index"): f'
' f'
' f'
' - f'

' + f'


' f'' f"" f"

{msg}

" From 387714a22132b581eea018a4ec7706ff5618a369 Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 30 Mar 2026 23:06:45 +0000 Subject: [PATCH 10/12] Move extra line break after note for spacing before tags --- handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/handlers.py b/handlers.py index 8f62d7d..1a53b8f 100644 --- a/handlers.py +++ b/handlers.py @@ -364,8 +364,8 @@ def handle_add_form(msg="", action_type="index"): f'

' f'
' f'
' - f'
' - f'


' + f'

' + f'

' f'' f"" f"

{msg}

" From f2f4682fa1cdeda16c81ff8dbfa59b16752923cb Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 30 Mar 2026 23:13:00 +0000 Subject: [PATCH 11/12] Hide toggle for now --- handlers.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/handlers.py b/handlers.py index 1a53b8f..d632ca9 100644 --- a/handlers.py +++ b/handlers.py @@ -357,26 +357,13 @@ def handle_add_form(msg="", action_type="index"): f"

Add a site to your index

" f'
' f'{_csrf_field()}' - f'' - f'' - f'' - f'' - f'

' - f'
' - f'
' + f'
' f'

' f'

' f'' f"
" f"

{msg}

" f'back' - f'' ) From 696a32cef9d831d73274522df04c3506183542b3 Mon Sep 17 00:00:00 2001 From: lichenblankie Date: Mon, 30 Mar 2026 23:14:54 +0000 Subject: [PATCH 12/12] Update add form --- handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers.py b/handlers.py index d632ca9..1665cae 100644 --- a/handlers.py +++ b/handlers.py @@ -357,7 +357,7 @@ def handle_add_form(msg="", action_type="index"): f"

Add a site to your index

" f'
' f'{_csrf_field()}' - f'
' + f'

' f'

' f'

' f''