···28 // pinnedRepositories: Any ATURI, it is up to appviews to validate these fields.
29 PinnedRepositories []string `json:"pinnedRepositories,omitempty" cborgen:"pinnedRepositories,omitempty"`
30 Stats []string `json:"stats,omitempty" cborgen:"stats,omitempty"`
031}
···28 // pinnedRepositories: Any ATURI, it is up to appviews to validate these fields.
29 PinnedRepositories []string `json:"pinnedRepositories,omitempty" cborgen:"pinnedRepositories,omitempty"`
30 Stats []string `json:"stats,omitempty" cborgen:"stats,omitempty"`
31+ Pronouns *string `json:"pronouns,omitempty" cborgen:"pronouns,omitempty"`
32}
···13 <title>login · tangled</title>
14 </head>
15 <body class="flex items-center justify-center min-h-screen">
16+ <main class="max-w-md px-7 mt-4">
17 <h1 class="flex place-content-center text-3xl font-semibold italic dark:text-white" >
18 {{ template "fragments/logotype" }}
19 </h1>
···21 tightly-knit social coding.
22 </h2>
23 <form
24+ class="mt-4"
25 hx-post="/login"
26 hx-swap="none"
27 hx-disabled-elt="#login-button"
···29 <div class="flex flex-col">
30 <label for="handle">handle</label>
31 <input
32+ autocapitalize="none"
33+ autocorrect="off"
34+ autocomplete="username"
35 type="text"
36 id="handle"
37 name="handle"
···56 <span>login</span>
57 </button>
58 </form>
59+ {{ if .ErrorCode }}
60+ <div class="flex gap-2 my-2 bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-3 py-2 text-red-500 dark:text-red-300">
61+ <span class="py-1">{{ i "circle-alert" "w-4 h-4" }}</span>
62+ <div>
63+ <h5 class="font-medium">Login error</h5>
64+ <p class="text-sm">
65+ {{ if eq .ErrorCode "access_denied" }}
66+ You have not authorized the app.
67+ {{ else if eq .ErrorCode "session" }}
68+ Server failed to create user session.
69+ {{ else }}
70+ Internal Server error.
71+ {{ end }}
72+ Please try again.
73+ </p>
74+ </div>
75+ </div>
76+ {{ end }}
77 <p class="text-sm text-gray-500">
78 Don't have an account? <a href="/signup" class="underline">Create an account</a> on Tangled now!
79 </p>
+23
appview/pagination/page.go
···1package pagination
2003type Page struct {
4 Offset int // where to start from
5 Limit int // number of items in a page
···10 Offset: 0,
11 Limit: 30,
12 }
00000000000000000000013}
1415func (p Page) Previous() Page {
···1package pagination
23+import "context"
4+5type Page struct {
6 Offset int // where to start from
7 Limit int // number of items in a page
···12 Offset: 0,
13 Limit: 30,
14 }
15+}
16+17+type ctxKey struct{}
18+19+func IntoContext(ctx context.Context, page Page) context.Context {
20+ return context.WithValue(ctx, ctxKey{}, page)
21+}
22+23+func FromContext(ctx context.Context) Page {
24+ if ctx == nil {
25+ return FirstPage()
26+ }
27+ v := ctx.Value(ctxKey{})
28+ if v == nil {
29+ return FirstPage()
30+ }
31+ page, ok := v.(Page)
32+ if !ok {
33+ return FirstPage()
34+ }
35+ return page
36}
3738func (p Page) Previous() Page {
···1+package validator
2+3+import (
4+ "fmt"
5+ "strings"
6+7+ "tangled.org/core/patchutil"
8+)
9+10+func (v *Validator) ValidatePatch(patch *string) error {
11+ if patch == nil || *patch == "" {
12+ return fmt.Errorf("patch is empty")
13+ }
14+15+ // add newline if not present to diff style patches
16+ if !patchutil.IsFormatPatch(*patch) && !strings.HasSuffix(*patch, "\n") {
17+ *patch = *patch + "\n"
18+ }
19+20+ if err := patchutil.IsPatchValid(*patch); err != nil {
21+ return err
22+ }
23+24+ return nil
25+}
+2-1
docs/knot-hosting.md
···39```
4041Next, move the `knot` binary to a location owned by `root` --
42-`/usr/local/bin/knot` is a good choice:
4344```
45sudo mv knot /usr/local/bin/knot
046```
4748This is necessary because SSH `AuthorizedKeysCommand` requires [really
···39```
4041Next, move the `knot` binary to a location owned by `root` --
42+`/usr/local/bin/` is a good choice. Make sure the binary itself is also owned by `root`:
4344```
45sudo mv knot /usr/local/bin/knot
46+sudo chown root:root /usr/local/bin/knot
47```
4849This is necessary because SSH `AuthorizedKeysCommand` requires [really