Subject: [PATCH links] Improve navigation when performing actions on bookmark listings. Marking as read/unread, star/unstar and deleting links will now redirect you back to the same page (filters, etc. if present) as before.

From: Peter Sanchez <[email-masked]>
Date: Tue, 6 Jan 2026 07:33:47 -0600

Implements: https://todo.code.netlandish.com/~netlandish/links/121
Changelog-updated: Navigation when performing an action on a bookmark
  listing page.
---
 core/routes.go           | 66 +++++++++++++++++++++++++++++++---------
 templates/link_list.html | 10 +++---
 2 files changed, 57 insertions(+), 19 deletions(-)

diff --git a/core/routes.go b/core/routes.go
index e2eb8f2..ece5745 100644
--- a/core/routes.go
+++ b/core/routes.go
@@ -2271,6 +2271,12 @@ func (s *Service) OrgLinksList(c echo.Context) error {
 		hasStarredFilter,
 		hasAllFilter bool
 	)
+	var (
+		tag, excludeTag, search string
+		isAltered               bool
+	)
+	queries := make(url.Values)
+
 	if c.QueryParam("filter") != "" {
 		op.Var("filter", c.QueryParam("filter"))
 		switch c.QueryParam("filter") {
@@ -2285,12 +2291,6 @@ func (s *Service) OrgLinksList(c echo.Context) error {
 		hasAllFilter = true
 	}
 
-	var (
-		tag, excludeTag, search string
-		isAltered               bool
-	)
-	queries := make(url.Values)
-
 	if c.QueryParam("tag") != "" {
 		tag = c.QueryParam("tag")
 		op.Var("tag", tag)
@@ -2427,6 +2427,28 @@ func (s *Service) OrgLinksList(c echo.Context) error {
 		slices.Reverse(orgLinks)
 	}
 
+	actionReturnURL := currURL
+	actionParams := make(url.Values)
+	if c.QueryParam("filter") != "" {
+		actionParams.Add("filter", c.QueryParam("filter"))
+	}
+	if c.QueryParam("tag") != "" {
+		actionParams.Add("tag", c.QueryParam("tag"))
+	}
+	if c.QueryParam("exclude") != "" {
+		actionParams.Add("exclude", c.QueryParam("exclude"))
+	}
+	if c.QueryParam("q") != "" {
+		actionParams.Add("q", c.QueryParam("q"))
+	}
+	if c.QueryParam("order") != "" {
+		actionParams.Add("order", c.QueryParam("order"))
+	}
+	if actionParams.Encode() != "" {
+		actionReturnURL = currURL + "?" + actionParams.Encode()
+	}
+	actionReturnURL = url.QueryEscape(actionReturnURL)
+
 	seoData := links.GetSEOData(c)
 	url := links.GetLinksDomainURL(c)
 	if isOrgLink {
@@ -2458,6 +2480,7 @@ func (s *Service) OrgLinksList(c echo.Context) error {
 		"hasAllFilter":      hasAllFilter,
 		"currURL":           currURL,
 		"queries":           template.URL(queries.Encode()),
+		"actionReturnURL":   template.URL(actionReturnURL),
 		"autoCompleteOrgID": org.ID,
 		"rssURL":            rssURL,
 		"followAction":      followAction,
@@ -2631,8 +2654,11 @@ func (s *Service) OrgLinkDelete(c echo.Context) error {
 		}
 
 		messages.Success(c, lt.Translate("Bookmark successfully deleted"))
-		return c.Redirect(http.StatusMovedPermanently,
-			c.Echo().Reverse(s.RouteName("home_link_list")))
+		redirect := c.QueryParam("next")
+		if redirect == "" {
+			redirect = c.Echo().Reverse(s.RouteName("home_link_list"))
+		}
+		return c.Redirect(http.StatusMovedPermanently, redirect)
 	}
 
 	// NOTE: All these validations already happen in the resolver for the POST request
@@ -2669,10 +2695,18 @@ func (s *Service) OrgLinkDelete(c echo.Context) error {
 	pd.Data["yes"] = lt.Translate("Yes")
 	pd.Data["cancel"] = lt.Translate("Cancel")
 	pd.Data["message"] = lt.Translate("Do you really whant to delete this bookmark?")
+
+	deleteURL := c.Echo().Reverse(s.RouteName("link_delete"), link.Hash)
+	backURL := c.Echo().Reverse(s.RouteName("home_link_list"))
+	if next := c.QueryParam("next"); next != "" {
+		deleteURL = deleteURL + "?next=" + url.QueryEscape(next)
+		backURL = next
+	}
+
 	gmap := gobwebs.Map{
 		"pd":   pd,
-		"url":  c.Echo().Reverse(s.RouteName("link_delete"), link.Hash),
-		"back": c.Echo().Reverse(s.RouteName("home_link_list")),
+		"url":  template.URL(deleteURL),
+		"back": backURL,
 	}
 	return s.Render(c, http.StatusOK, "element_delete.html", gmap)
 }
@@ -3345,8 +3379,10 @@ func (s *Service) OrgLinkStarToggle(c echo.Context) error {
 	if err != nil {
 		return err
 	}
-	req := c.Request()
-	redirect := req.Header.Get("Referer")
+	redirect := c.QueryParam("next")
+	if redirect == "" {
+		redirect = c.Request().Header.Get("Referer")
+	}
 	if redirect == "" {
 		redirect = c.Echo().Reverse(s.RouteName("home_link_list"))
 	}
@@ -3393,8 +3429,10 @@ func (s *Service) OrgLinkAsReadToggle(c echo.Context) error {
 	if err != nil {
 		return err
 	}
-	req := c.Request()
-	redirect := req.Header.Get("Referer")
+	redirect := c.QueryParam("next")
+	if redirect == "" {
+		redirect = c.Request().Header.Get("Referer")
+	}
 	if redirect == "" {
 		redirect = c.Echo().Reverse(s.RouteName("home_link_list"))
 	}
diff --git a/templates/link_list.html b/templates/link_list.html
index 0676206..88f5eaa 100644
--- a/templates/link_list.html
+++ b/templates/link_list.html
@@ -136,13 +136,13 @@
             {{if eq .UserID $.currentUserID}}
                 <!-- Read/Unread -->
                 {{if .Unread}}
-                <a class="icon-link tooltip-link" href="{{reverse "core:link_mark_as_read" .Hash}}" data-tooltip="{{$.pd.Data.mark_as_read}}">
+                <a class="icon-link tooltip-link" href="{{reverse "core:link_mark_as_read" .Hash}}?next={{$.actionReturnURL}}" data-tooltip="{{$.pd.Data.mark_as_read}}">
                     <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width:20px;">
                         <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
                     </svg>
                 </a>
                 {{else}}
-                <a class="icon-link tooltip-link" href="{{reverse "core:link_mark_as_read" .Hash}}" data-tooltip="{{$.pd.Data.mark_as_unread}}">
+                <a class="icon-link tooltip-link" href="{{reverse "core:link_mark_as_read" .Hash}}?next={{$.actionReturnURL}}" data-tooltip="{{$.pd.Data.mark_as_unread}}">
                     <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" style="width:20px;">
                         <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
                     </svg>
@@ -151,13 +151,13 @@
 
                 <!-- Star/Unstar -->
                 {{if .Starred}}
-                <a class="icon-link tooltip-lin tooltip-link" href="{{reverse "core:link_star_toggle" .Hash}}" data-tooltip="{{$.pd.Data.mark_as_non_starred}}">
+                <a class="icon-link tooltip-lin tooltip-link" href="{{reverse "core:link_star_toggle" .Hash}}?next={{$.actionReturnURL}}" data-tooltip="{{$.pd.Data.mark_as_non_starred}}">
                     <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width:20px;">
                         <path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
                     </svg>
                 </a>
                 {{else}}
-                <a class="icon-link non-starred tooltip-link" href="{{reverse "core:link_star_toggle" .Hash}}" data-tooltip="{{$.pd.Data.mark_as_starred}}">
+                <a class="icon-link non-starred tooltip-link" href="{{reverse "core:link_star_toggle" .Hash}}?next={{$.actionReturnURL}}" data-tooltip="{{$.pd.Data.mark_as_starred}}">
                     <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width:20px;">
                         <path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
                     </svg>
@@ -173,7 +173,7 @@
                 </a>
 
                 <!-- Delete -->
-                <a class="icon-link tooltip-link" href="{{reverse "core:link_delete" .Hash}}" data-tooltip="{{$.pd.Data.delete}}">
+                <a class="icon-link tooltip-link" href="{{reverse "core:link_delete" .Hash}}?next={{$.actionReturnURL}}" data-tooltip="{{$.pd.Data.delete}}">
                     <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6" style="width:20px;">
                         <path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
                     </svg>
-- 
2.52.0

---

From: "Peter Sanchez" <[email-masked]>
Date: Tue, 06 Jan 2026 07:37:25 -0600

Applied.

To [email-masked]:~netlandish/links
   e35dd79..902d902  master -> master