7 files changed,
+56
−23
internal/home/markup_test.go
+33 −0
internal/home/templates/layout.html
+1 −1
internal/home/templates/passkey_confirm.html
+8 −8
internal/home/templates/passkey_enrol.html
+2 −2
internal/home/templates/recovery_codes.html
+2 −2
internal/home/templates/signin.html
+7 −7
internal/home/templates/signin_confirm.html
+3 −3
| new file mode 100644 |
| index 0000000..c010d36 |
| --- /dev/null |
| +++ b/internal/home/markup_test.go |
| @@ -0,0 +1,33 @@ |
| +package home |
| + |
| +import ( |
| + "os" |
| + "path/filepath" |
| + "regexp" |
| + "strings" |
| + "testing" |
| +) |
| + |
| +func TestTemplateComponentSpelling(t *testing.T) { |
| + allowed := map[string]bool{"rst-sr-only": true, "rst-mono": true, "rst-m-hide": true, "rst-grow": true, "rst-nm": true, "rst-danger": true, "rst-cell-mut": true} |
| + classes := regexp.MustCompile(`(?i)(?:^|\s)class\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))`) |
| + paths, err := filepath.Glob("templates/*.html") |
| + if err != nil || len(paths) == 0 { |
| + t.Fatalf("template enumeration: %v", err) |
| + } |
| + for _, path := range paths { |
| + data, err := os.ReadFile(path) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + for _, match := range classes.FindAllStringSubmatch(string(data), -1) { |
| + for _, value := range match[1:] { |
| + for _, class := range strings.Fields(value) { |
| + if strings.HasPrefix(class, "rst-") && !allowed[class] { |
| + t.Errorf("%s: component class %q must use attribute spelling", path, class) |
| + } |
| + } |
| + } |
| + } |
| + } |
| +} |
| index 2275b90..9340be0 100644 |
| --- a/internal/home/templates/layout.html |
| +++ b/internal/home/templates/layout.html |
| @@ -8,7 +8,7 @@ |
| <link rel="stylesheet" href="/static/theme.css"> |
| </head> |
| <body{{block "bodyattrs" .}}{{end}}> |
| -<main class="rst-page"> |
| +<main rst-page> |
| {{template "content" .}} |
| </main> |
| {{block "scripts" .}}{{end}} |
| index 90d6f40..e6de3f2 100644 |
| --- a/internal/home/templates/passkey_confirm.html |
| +++ b/internal/home/templates/passkey_confirm.html |
| @@ -3,21 +3,21 @@ |
| {{define "content"}} |
| <h1>Confirm it is you</h1> |
| |
| -{{if .RecoveryFailed}}<p role="alert" class="rst-callout" data-tone="negative"><span class="rst-callout__body">That code did not work.</span></p>{{end}} |
| +{{if .RecoveryFailed}}<p role="alert" rst-callout rst-tone="negative"><span rst-callout-body>That code did not work.</span></p>{{end}} |
| |
| -<button id="passkey-confirm" type="button" class="rst-btn rst-btn--primary" data-passkey hidden>Use your passkey</button> |
| +<button id="passkey-confirm" type="button" rst-btn="primary" data-passkey hidden>Use your passkey</button> |
| <p id="passkey-status" role="status"></p> |
| |
| <h2>Or use a recovery code</h2> |
| <p>Recovery is exactly the moment a passkey is not working, so this form |
| needs no script and no device.</p> |
| -<form method="post" action="/passkey/signin/recovery" class="rst-form"> |
| - <div class="rst-field"> |
| - <label class="rst-field__label" for="code">Recovery code</label> |
| - <input id="code" name="code" type="text" autocomplete="one-time-code" required class="rst-input"> |
| +<form method="post" action="/passkey/signin/recovery" rst-form> |
| + <div rst-field> |
| + <label rst-field-label for="code">Recovery code</label> |
| + <input id="code" name="code" type="text" autocomplete="one-time-code" required rst-input> |
| </div> |
| - <div class="rst-form-foot"> |
| - <button type="submit" class="rst-btn rst-btn--primary">Sign in</button> |
| + <div rst-form-foot> |
| + <button type="submit" rst-btn="primary">Sign in</button> |
| </div> |
| </form> |
| {{end}} |
| index a57d3df..a2edcb5 100644 |
| --- a/internal/home/templates/passkey_enrol.html |
| +++ b/internal/home/templates/passkey_enrol.html |
| @@ -4,8 +4,8 @@ |
| <h1>Add a passkey</h1> |
| <p>A passkey is an optional second step. Without one, your email link is |
| the only thing standing between someone with your inbox and your account.</p> |
| -<button id="passkey-enrol" type="button" class="rst-btn rst-btn--primary" data-passkey hidden>Add a passkey</button> |
| +<button id="passkey-enrol" type="button" rst-btn="primary" data-passkey hidden>Add a passkey</button> |
| <p id="passkey-status" role="status"></p> |
| -<noscript><p class="rst-callout"><span class="rst-callout__body">Adding a passkey needs JavaScript. Everything else here does not.</span></p></noscript> |
| +<noscript><p rst-callout><span rst-callout-body>Adding a passkey needs JavaScript. Everything else here does not.</span></p></noscript> |
| {{end}} |
| {{define "scripts"}}<script type="module" src="/static/passkey.js"></script>{{end}} |
| index 90c7698..d88ca24 100644 |
| --- a/internal/home/templates/recovery_codes.html |
| +++ b/internal/home/templates/recovery_codes.html |
| @@ -2,7 +2,7 @@ |
| {{define "content"}} |
| <h1>Recovery codes</h1> |
| {{if .Codes}} |
| -<p class="rst-callout" data-tone="warning"><span class="rst-callout__body"><strong>These are shown once.</strong> Keep them somewhere your |
| +<p rst-callout rst-tone="warning"><span rst-callout-body><strong>These are shown once.</strong> Keep them somewhere your |
| passkey is not. Each one works a single time.</span></p> |
| <ul>{{range .Codes}}<li><code>{{.}}</code></li>{{end}}</ul> |
| <p>Losing your passkey and these codes together locks you out permanently. |
| @@ -11,7 +11,7 @@ There is no operator who can undo that, by design.</p> |
| <p>You have {{.Remaining}} unused recovery codes.</p> |
| <p>Generating a new set <strong>invalidates every code you have now</strong>.</p> |
| <form method="post" action="/passkey/codes"> |
| - <button type="submit" class="rst-btn rst-btn--danger">Generate a new set</button> |
| + <button type="submit" rst-btn="danger">Generate a new set</button> |
| </form> |
| {{end}} |
| {{end}} |
| index 6a81536..94b0e20 100644 |
| --- a/internal/home/templates/signin.html |
| +++ b/internal/home/templates/signin.html |
| @@ -1,14 +1,14 @@ |
| {{define "title"}}Sign in · Oficina{{end}} |
| {{define "content"}} |
| <h1>Sign in</h1> |
| -{{if .Error}}<p role="alert" class="rst-callout" data-tone="negative"><span class="rst-callout__body">{{.Error}}</span></p>{{end}} |
| -<form method="post" action="/signin" class="rst-form"> |
| - <div class="rst-field"> |
| - <label class="rst-field__label" for="address">Email address</label> |
| - <input id="address" name="address" type="email" autocomplete="email" required autofocus class="rst-input"> |
| +{{if .Error}}<p role="alert" rst-callout rst-tone="negative"><span rst-callout-body>{{.Error}}</span></p>{{end}} |
| +<form method="post" action="/signin" rst-form> |
| + <div rst-field> |
| + <label rst-field-label for="address">Email address</label> |
| + <input id="address" name="address" type="email" autocomplete="email" required autofocus rst-input> |
| </div> |
| - <div class="rst-form-foot"> |
| - <button type="submit" class="rst-btn rst-btn--primary">Continue</button> |
| + <div rst-form-foot> |
| + <button type="submit" rst-btn="primary">Continue</button> |
| </div> |
| </form> |
| {{end}} |
| index 9446f4d..b27fe86 100644 |
| --- a/internal/home/templates/signin_confirm.html |
| +++ b/internal/home/templates/signin_confirm.html |
| @@ -2,10 +2,10 @@ |
| {{define "content"}} |
| <h1>Finish signing in</h1> |
| <p>Press the button to sign in on this device.</p> |
| -<form method="post" action="/signin/verify" class="rst-form"> |
| +<form method="post" action="/signin/verify" rst-form> |
| <input type="hidden" name="token" value="{{.Token}}"> |
| - <div class="rst-form-foot"> |
| - <button type="submit" class="rst-btn rst-btn--primary">Sign in</button> |
| + <div rst-form-foot> |
| + <button type="submit" rst-btn="primary">Sign in</button> |
| </div> |
| </form> |
| {{end}} |