Skip to content

Commit 56c1103

Browse files
authored
Merge pull request #158 from arran4/codex/move-to-1-based-indexing-and-adjust-urls
Simplify tab edit route naming
2 parents 8d1a334 + 55d1345 commit 56c1103

File tree

12 files changed

+182
-65
lines changed

12 files changed

+182
-65
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Every command must be on its own line; empty lines are ignored.
117117
| `<Link> <Name>` | Create a link to `<Link>` with the display name `<Name>`. |
118118
| `Column` | Start a new column. |
119119
| `Page[: <name>]` | Create a new page and optionally name it. |
120-
| `Tab[: <name>]` | Start a new tab. Without a name it reverts to the main tab (switch using `?tab=<index>`).|
120+
| `Tab[: <name>]` | Start a new tab. Without a name it reverts to the main tab (switch using `/tab/<index>`).|
121121
| `--` | Insert a horizontal rule and reset columns. |
122122

123123
Tabs contain one or more pages. The first tab is implicit and does not need a `Tab` directive unless you want to name it. Each `Page` line begins a new page within the current tab.

cmd/gobookmarks/serve.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"os/signal"
2828
"path/filepath"
2929
"reflect"
30+
"strconv"
3031
"strings"
3132
"sync"
3233
"time"
@@ -265,6 +266,8 @@ func (c *ServeCommand) Execute(args []string) error {
265266

266267
// News
267268
r.Handle("/", http.HandlerFunc(runTemplate("mainPage.gohtml"))).Methods("GET")
269+
r.Handle("/tab", http.HandlerFunc(runTemplate("mainPage.gohtml"))).Methods("GET")
270+
r.Handle("/tab/{tab}", http.HandlerFunc(runTemplate("mainPage.gohtml"))).Methods("GET")
268271
r.HandleFunc("/", runHandlerChain(TaskDoneAutoRefreshPage)).Methods("POST")
269272

270273
r.HandleFunc("/edit", runTemplate("loginPage.gohtml")).Methods("GET").MatcherFunc(gorillamuxlogic.Not(RequiresAnAccount()))
@@ -301,6 +304,12 @@ func (c *ServeCommand) Execute(args []string) error {
301304
r.HandleFunc("/editTab", runHandlerChain(TabEditSaveAction, TaskDoneAutoRefreshPage)).Methods("POST").MatcherFunc(RequiresAnAccount()).MatcherFunc(TaskMatcher(TaskSaveAndDone))
302305
r.HandleFunc("/editTab", runHandlerChain(TabEditSaveAction, StopEditMode, redirectToHandlerBranchToRef("/"))).Methods("POST").MatcherFunc(RequiresAnAccount()).MatcherFunc(TaskMatcher(TaskSaveAndStopEditing))
303306
r.HandleFunc("/editTab", runHandlerChain(TaskDoneAutoRefreshPage)).Methods("POST")
307+
r.HandleFunc("/tab/{tab}/edit", runTemplate("loginPage.gohtml")).Methods("GET").MatcherFunc(gorillamuxlogic.Not(RequiresAnAccount()))
308+
r.HandleFunc("/tab/{tab}/edit", runHandlerChain(EditTabPage)).Methods("GET").MatcherFunc(RequiresAnAccount())
309+
r.HandleFunc("/tab/{tab}/edit", runHandlerChain(TabEditSaveAction, redirectToHandlerBranchToRef("/"))).Methods("POST").MatcherFunc(RequiresAnAccount()).MatcherFunc(TaskMatcher(TaskSave))
310+
r.HandleFunc("/tab/{tab}/edit", runHandlerChain(TabEditSaveAction, TaskDoneAutoRefreshPage)).Methods("POST").MatcherFunc(RequiresAnAccount()).MatcherFunc(TaskMatcher(TaskSaveAndDone))
311+
r.HandleFunc("/tab/{tab}/edit", runHandlerChain(TabEditSaveAction, StopEditMode, redirectToHandlerBranchToRef("/"))).Methods("POST").MatcherFunc(RequiresAnAccount()).MatcherFunc(TaskMatcher(TaskSaveAndStopEditing))
312+
r.HandleFunc("/tab/{tab}/edit", runHandlerChain(TaskDoneAutoRefreshPage)).Methods("POST")
304313

305314
r.HandleFunc("/editPage", runTemplate("loginPage.gohtml")).Methods("GET").MatcherFunc(gorillamuxlogic.Not(RequiresAnAccount()))
306315
r.HandleFunc("/editPage", runHandlerChain(EditPagePage)).Methods("GET").MatcherFunc(RequiresAnAccount())
@@ -311,7 +320,9 @@ func (c *ServeCommand) Execute(args []string) error {
311320

312321
r.HandleFunc("/moveTab", runHandlerChain(MoveTabAction)).Methods("POST").MatcherFunc(RequiresAnAccount())
313322
r.HandleFunc("/movePage", runHandlerChain(MovePageAction)).Methods("POST").MatcherFunc(RequiresAnAccount())
323+
r.HandleFunc("/tab/{tab}/movePage", runHandlerChain(MovePageAction)).Methods("POST").MatcherFunc(RequiresAnAccount())
314324
r.HandleFunc("/moveEntry", runHandlerChain(MoveEntryAction)).Methods("POST").MatcherFunc(RequiresAnAccount())
325+
r.HandleFunc("/tab/{tab}/moveEntry", runHandlerChain(MoveEntryAction)).Methods("POST").MatcherFunc(RequiresAnAccount())
315326

316327
r.HandleFunc("/history", runTemplate("loginPage.gohtml")).Methods("GET").MatcherFunc(gorillamuxlogic.Not(RequiresAnAccount()))
317328
r.HandleFunc("/history", runTemplate("history.gohtml")).Methods("GET").MatcherFunc(RequiresAnAccount())
@@ -623,19 +634,19 @@ func redirectToHandlerBranchToRef(toUrl string) func(http.ResponseWriter, *http.
623634
u, _ := url.Parse(toUrl)
624635
qs := u.Query()
625636
qs.Set("ref", "refs/heads/"+r.PostFormValue("branch"))
626-
tab := r.PostFormValue("tab")
637+
tab := TabFromRequest(r)
627638
if v, ok := r.Context().Value(ContextValues("redirectTab")).(string); ok {
628-
tab = v
629-
}
630-
if tab != "" {
631-
qs.Set("tab", tab)
639+
if parsed, err := strconv.Atoi(v); err == nil {
640+
tab = parsed
641+
}
632642
}
643+
u.Path = TabPath(tab)
633644
page := r.PostFormValue("page")
634645
if v, ok := r.Context().Value(ContextValues("redirectPage")).(string); ok {
635646
page = v
636647
}
637-
if page != "" {
638-
u.Fragment = "page" + page
648+
if fragment := PageFragmentFromIndex(page); fragment != "" {
649+
u.Fragment = fragment
639650
}
640651
if edit := r.URL.Query().Get("edit"); edit != "" {
641652
qs.Set("edit", edit)
@@ -649,11 +660,9 @@ func redirectToHandlerTabPage(toUrl string) func(http.ResponseWriter, *http.Requ
649660
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
650661
u, _ := url.Parse(toUrl)
651662
qs := u.Query()
652-
if tab := r.URL.Query().Get("tab"); tab != "" {
653-
qs.Set("tab", tab)
654-
}
655-
if page := r.URL.Query().Get("page"); page != "" {
656-
u.Fragment = "page" + page
663+
u.Path = TabPath(TabFromRequest(r))
664+
if fragment := PageFragmentFromIndex(r.URL.Query().Get("page")); fragment != "" {
665+
u.Fragment = fragment
657666
}
658667
if edit := r.URL.Query().Get("edit"); edit != "" {
659668
qs.Set("edit", edit)

core.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"log"
1010
"net/http"
1111
"os"
12-
"strconv"
1312
"strings"
1413
)
1514

@@ -39,12 +38,7 @@ func CoreAdderMiddleware(next http.Handler) http.Handler {
3938

4039
ctx := context.WithValue(request.Context(), ContextValues("provider"), providerName)
4140
editMode := request.URL.Query().Get("edit") == "1"
42-
tab := 0
43-
if tabS := request.URL.Query().Get("tab"); tabS != "" {
44-
if tabI, err := strconv.Atoi(tabS); err == nil {
45-
tab = tabI
46-
}
47-
}
41+
tab := TabFromRequest(request)
4842
ctx = context.WithValue(ctx, ContextValues("coreData"), &CoreData{
4943
UserRef: login,
5044
Title: title,

data_test.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,23 @@ func testFuncMap() template.FuncMap {
5959
}
6060
return 0
6161
},
62-
"atoi": func(s string) int { i, _ := strconv.Atoi(s); return i },
63-
"tab": func() string { return "0" },
64-
"tabName": func() string { return "Main" },
65-
"page": func() string { return "" },
66-
"historyRef": func() string { return "refs/heads/main" },
67-
"useCssColumns": func() bool { return false },
68-
"devMode": func() bool { return false },
69-
"showFooter": func() bool { return true },
70-
"showPages": func() bool { return true },
71-
"loggedIn": func() (bool, error) { return true, nil },
62+
"atoi": func(s string) int { i, _ := strconv.Atoi(s); return i },
63+
"tab": func() string { return "0" },
64+
"tabPath": func(tab int) string { return "/" },
65+
"tabEditPath": func(tab int) string { return TabEditPath(tab) },
66+
"tabEditHref": func(tab int, ref, name string) string { return TabEditHref(tab, ref, name) },
67+
"currentTabPath": func() string { return "/" },
68+
"appendQuery": func(rawURL string, params ...string) string { return AppendQueryParams(rawURL, params...) },
69+
"tabName": func() string { return "Main" },
70+
"page": func() string { return "" },
71+
"historyRef": func() string { return "refs/heads/main" },
72+
"useCssColumns": func() bool { return false },
73+
"devMode": func() bool { return false },
74+
"showFooter": func() bool { return true },
75+
"showPages": func() bool { return true },
76+
"loggedIn": func() (bool, error) { return true, nil },
7277
"bookmarkTabs": func() ([]TabInfo, error) {
73-
return []TabInfo{{Index: 0, Name: "", IndexName: "Main", Href: "/", LastPageSha: ""}}, nil
78+
return []TabInfo{{Index: 0, Name: "", IndexName: "Main", Href: "/", EditHref: "/?edit=1", LastPageSha: ""}}, nil
7479
},
7580
"commitShort": func() string {
7681
short := commit

funcs.go

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type TabInfo struct {
1818
Name string
1919
IndexName string
2020
Href string
21+
EditHref string
2122
LastPageSha string
2223
}
2324

@@ -85,7 +86,22 @@ func NewFuncs(r *http.Request) template.FuncMap {
8586
return r.URL.Query().Get("ref")
8687
},
8788
"tab": func() string {
88-
return r.URL.Query().Get("tab")
89+
return strconv.Itoa(TabFromRequest(r))
90+
},
91+
"tabPath": func(tab int) string {
92+
return TabPath(tab)
93+
},
94+
"tabEditPath": func(tab int) string {
95+
return TabEditPath(tab)
96+
},
97+
"currentTabPath": func() string {
98+
return TabPath(TabFromRequest(r))
99+
},
100+
"tabEditHref": func(tab int, ref, name string) string {
101+
return TabEditHref(tab, ref, name)
102+
},
103+
"appendQuery": func(rawURL string, params ...string) string {
104+
return AppendQueryParams(rawURL, params...)
89105
},
90106
"page": func() string {
91107
return r.URL.Query().Get("page")
@@ -210,9 +226,8 @@ func NewFuncs(r *http.Request) template.FuncMap {
210226
bookmark = bookmarks
211227
}
212228
tabs := ParseBookmarks(bookmark)
213-
tabStr := r.URL.Query().Get("tab")
214-
idx, err := strconv.Atoi(tabStr)
215-
if err != nil || idx < 0 || idx >= len(tabs) {
229+
idx := TabFromRequest(r)
230+
if idx < 0 || idx >= len(tabs) {
216231
idx = 0
217232
}
218233
return tabs[idx].Pages, nil
@@ -247,22 +262,12 @@ func NewFuncs(r *http.Request) template.FuncMap {
247262
indexName = "Main"
248263
}
249264
if indexName != "" {
250-
q := make([]string, 0, 2)
251-
if i != 0 {
252-
q = append(q, "tab="+strconv.Itoa(i))
253-
}
254-
if ref != "" {
255-
q = append(q, "ref="+ref)
256-
}
257-
href := "/"
258-
if len(q) > 0 {
259-
href = "/?" + strings.Join(q, "&")
260-
}
265+
href := TabHref(i, ref)
261266
lastSha := ""
262267
if len(t.Pages) > 0 {
263268
lastSha = t.Pages[len(t.Pages)-1].Sha()
264269
}
265-
tabs = append(tabs, TabInfo{Index: i, Name: t.Name, IndexName: indexName, Href: href, LastPageSha: lastSha})
270+
tabs = append(tabs, TabInfo{Index: i, Name: t.Name, IndexName: indexName, Href: href, EditHref: AppendQueryParams(href, "edit", "1"), LastPageSha: lastSha})
266271
}
267272
}
268273
return tabs, nil
@@ -289,9 +294,8 @@ func NewFuncs(r *http.Request) template.FuncMap {
289294
bookmark = bookmarks
290295
}
291296
tabs := ParseBookmarks(bookmark)
292-
tabStr := r.URL.Query().Get("tab")
293-
idx, err := strconv.Atoi(tabStr)
294-
if err != nil || idx < 0 || idx >= len(tabs) {
297+
idx := TabFromRequest(r)
298+
if idx < 0 || idx >= len(tabs) {
295299
idx = 0
296300
}
297301
name := tabs[idx].DisplayName()

moveHandlers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func MoveTabAction(w http.ResponseWriter, r *http.Request) error {
3737
func MovePageAction(w http.ResponseWriter, r *http.Request) error {
3838
from, _ := strconv.Atoi(r.URL.Query().Get("from"))
3939
to, _ := strconv.Atoi(r.URL.Query().Get("to"))
40-
tabIdx, _ := strconv.Atoi(r.URL.Query().Get("tab"))
40+
tabIdx := TabFromRequest(r)
4141
session := r.Context().Value(ContextValues("session")).(*sessions.Session)
4242
githubUser, _ := session.Values["GithubUser"].(*User)
4343
token, _ := session.Values["Token"].(*oauth2.Token)
@@ -67,7 +67,7 @@ func MoveEntryAction(w http.ResponseWriter, r *http.Request) error {
6767
from, _ := strconv.Atoi(r.URL.Query().Get("from"))
6868
to, _ := strconv.Atoi(r.URL.Query().Get("to"))
6969
catIdx, _ := strconv.Atoi(r.URL.Query().Get("category"))
70-
tabIdx, _ := strconv.Atoi(r.URL.Query().Get("tab"))
70+
tabIdx := TabFromRequest(r)
7171
pageIdx, _ := strconv.Atoi(r.URL.Query().Get("page"))
7272
session := r.Context().Value(ContextValues("session")).(*sessions.Session)
7373
githubUser, _ := session.Values["GithubUser"].(*User)

pageEditHandlers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func EditPagePage(w http.ResponseWriter, r *http.Request) error {
1414
githubUser, _ := session.Values["GithubUser"].(*User)
1515
token, _ := session.Values["Token"].(*oauth2.Token)
1616
ref := r.URL.Query().Get("ref")
17-
tabIdx, _ := strconv.Atoi(r.URL.Query().Get("tab"))
17+
tabIdx := TabFromRequest(r)
1818
pageIdx, _ := strconv.Atoi(r.URL.Query().Get("page"))
1919

2020
login := ""
@@ -62,7 +62,7 @@ func PageEditSaveAction(w http.ResponseWriter, r *http.Request) error {
6262
branch := r.PostFormValue("branch")
6363
ref := r.PostFormValue("ref")
6464
sha := r.PostFormValue("sha")
65-
tabIdx, _ := strconv.Atoi(r.PostFormValue("tab"))
65+
tabIdx := TabFromRequest(r)
6666
pageIdx, pageErr := strconv.Atoi(r.PostFormValue("page"))
6767

6868
session := r.Context().Value(ContextValues("session")).(*sessions.Session)

tabEditHandlers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
func EditTabPage(w http.ResponseWriter, r *http.Request) error {
1414
tabName := r.URL.Query().Get("name")
15-
tabIdx, _ := strconv.Atoi(r.URL.Query().Get("tab"))
15+
tabIdx := TabFromRequest(r)
1616
session := r.Context().Value(ContextValues("session")).(*sessions.Session)
1717
githubUser, _ := session.Values["GithubUser"].(*User)
1818
token, _ := session.Values["Token"].(*oauth2.Token)
@@ -79,7 +79,7 @@ func TabEditSaveAction(w http.ResponseWriter, r *http.Request) error {
7979
text := r.PostFormValue("text")
8080
branch := r.PostFormValue("branch")
8181
ref := r.PostFormValue("ref")
82-
tabIdx, _ := strconv.Atoi(r.PostFormValue("tab"))
82+
tabIdx := TabFromRequest(r)
8383
sha := r.PostFormValue("sha")
8484

8585
session := r.Context().Value(ContextValues("session")).(*sessions.Session)

tab_utils.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package gobookmarks
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/url"
7+
"strconv"
8+
9+
"github.com/gorilla/mux"
10+
)
11+
12+
// TabFromRequest extracts the tab index from either the path parameters or the query string.
13+
func TabFromRequest(r *http.Request) int {
14+
if r == nil {
15+
return 0
16+
}
17+
if vars := mux.Vars(r); vars != nil {
18+
if tabStr, ok := vars["tab"]; ok {
19+
if tabIdx, err := strconv.Atoi(tabStr); err == nil {
20+
return tabIdx
21+
}
22+
}
23+
}
24+
if tabS := r.URL.Query().Get("tab"); tabS != "" {
25+
if tabI, err := strconv.Atoi(tabS); err == nil {
26+
return tabI
27+
}
28+
}
29+
if tabS := r.PostFormValue("tab"); tabS != "" {
30+
if tabI, err := strconv.Atoi(tabS); err == nil {
31+
return tabI
32+
}
33+
}
34+
return 0
35+
}
36+
37+
// TabPath returns the semantic path for a tab index (0 is the root tab).
38+
func TabPath(tab int) string {
39+
if tab <= 0 {
40+
return "/"
41+
}
42+
return fmt.Sprintf("/tab/%d", tab)
43+
}
44+
45+
// TabEditPath returns the edit endpoint for a tab index.
46+
func TabEditPath(tab int) string {
47+
if tab <= 0 {
48+
return "/editTab"
49+
}
50+
return fmt.Sprintf("/tab/%d/edit", tab)
51+
}
52+
53+
// TabHref builds the link to a tab, preserving ref when provided.
54+
func TabHref(tab int, ref string) string {
55+
path := TabPath(tab)
56+
if ref == "" {
57+
return path
58+
}
59+
return fmt.Sprintf("%s?ref=%s", path, url.QueryEscape(ref))
60+
}
61+
62+
// TabEditHref builds the link to edit a tab at the semantic path.
63+
func TabEditHref(tab int, ref, name string) string {
64+
path := TabEditPath(tab)
65+
params := []string{}
66+
if ref != "" {
67+
params = append(params, "ref", ref)
68+
}
69+
if name != "" {
70+
params = append(params, "name", name)
71+
}
72+
return AppendQueryParams(path, params...)
73+
}
74+
75+
// AppendQueryParams appends key/value query params to the provided URL string.
76+
func AppendQueryParams(rawURL string, params ...string) string {
77+
u, err := url.Parse(rawURL)
78+
if err != nil {
79+
return rawURL
80+
}
81+
q := u.Query()
82+
for i := 0; i+1 < len(params); i += 2 {
83+
q.Set(params[i], params[i+1])
84+
}
85+
u.RawQuery = q.Encode()
86+
return u.String()
87+
}
88+
89+
// PageFragmentFromIndex converts a zero-based page index string to a 1-based fragment identifier.
90+
func PageFragmentFromIndex(pageStr string) string {
91+
if pageStr == "" {
92+
return ""
93+
}
94+
if pageIdx, err := strconv.Atoi(pageStr); err == nil {
95+
return fmt.Sprintf("page%d", pageIdx+1)
96+
}
97+
return "page" + pageStr
98+
}

templates/dragdrop.gohtml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@ document.addEventListener('DOMContentLoaded', () => {
3838
const tabList = document.getElementById('tab-list');
3939
enableDragSort(tabList, (f,t)=>`/moveTab?from=${f}&to=${t}`);
4040
const pageList = document.getElementById('page-list');
41-
const currentTab = document.body.dataset.tab || '';
42-
enableDragSort(pageList, (f,t)=>`/movePage?tab=${encodeURIComponent(currentTab)}&from=${f}&to=${t}`);
41+
const currentTab = document.body.dataset.tab || '0';
42+
const tabPrefix = currentTab && currentTab !== '0' ? `/tab/${encodeURIComponent(currentTab)}` : '';
43+
enableDragSort(pageList, (f,t)=>`${tabPrefix}/movePage?from=${f}&to=${t}`);
4344
document.querySelectorAll('.bookmark-entries').forEach(ul => {
4445
const cat = ul.dataset.index;
4546
const page = ul.dataset.page;
46-
enableDragSort(ul, (f,t)=>`/moveEntry?category=${cat}&page=${page}&tab=${encodeURIComponent(currentTab)}&from=${f}&to=${t}`);
47+
enableDragSort(ul, (f,t)=>`${tabPrefix}/moveEntry?category=${cat}&page=${page}&from=${f}&to=${t}`);
4748
});
4849
});
4950
</script>

0 commit comments

Comments
 (0)