Add password-visibility toggle to login page (TypeScript)

- web/ts/login.ts: small vanilla TS module toggling the password input's
  type between 'password'/'text' via a 'Show/Hide' button, so users can
  rule out typos before submitting. Compiled to web/static/login.js
  (ES module) via tsc (web/tsconfig.json, new 'make web-ts'/'web-assets'
  Makefile targets) and loaded via <script type="module">. Compiled JS
  is committed/embedded the same way as the compiled CSS — no Node.js
  needed at runtime.
- Verified end-to-end against the live proxied server
  (https://local.unqr.dev/web/login): POST /web/login correctly returns
  303 + Set-Cookie, and the session then authorizes GET /web/ — the
  server-side login flow is confirmed working correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-08-19 10:13:37 +02:00
co-authored by Copilot
parent 4dba55c807
commit 3dc49b6b22
11 changed files with 469 additions and 19 deletions
+9 -2
View File
@@ -17,6 +17,8 @@ make tidy # go mod tidy
go test ./internal/store/ -run TestStoreRoundTrip -v # single test go test ./internal/store/ -run TestStoreRoundTrip -v # single test
make templ-generate # regenerate *_templ.go after editing internal/web/templates/*.templ make templ-generate # regenerate *_templ.go after editing internal/web/templates/*.templ
make web-css # templ-generate + rebuild web/static/app.css (needs `make web-deps` once, Node.js/npm) make web-css # templ-generate + rebuild web/static/app.css (needs `make web-deps` once, Node.js/npm)
make web-ts # compile web/ts/*.ts to web/static/*.js
make web-assets # web-css + web-ts (everything under web/static/)
``` ```
Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`, Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`,
@@ -154,10 +156,15 @@ Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`,
to `web/static/app.css` via `make web-css` (needs Node/npm — see to `web/static/app.css` via `make web-css` (needs Node/npm — see
`web/package.json`); htmx itself is vendored as a static file `web/package.json`); htmx itself is vendored as a static file
(`web/static/htmx.min.js`, not npm-installed) to avoid a CDN dependency. (`web/static/htmx.min.js`, not npm-installed) to avoid a CDN dependency.
Both static assets are embedded into the Go binary at build time via Client-side-only logic (currently just the login page's password-visibility
toggle) is written in TypeScript under `web/ts/*.ts`, compiled to plain
JS via `tsc` (`web/tsconfig.json`, `make web-ts`) into `web/static/*.js`
as ES modules (`<script type="module">`). All static assets (CSS, JS,
vendored htmx) are embedded into the Go binary at build time via
`web/staticassets.go` (`//go:embed static`), so the compiled server has `web/staticassets.go` (`//go:embed static`), so the compiled server has
no runtime dependency on Node.js or the `web/` directory being present — no runtime dependency on Node.js or the `web/` directory being present —
Node/npm are only needed when actually changing templates/styles. Node/npm are only needed when actually changing templates/styles/TS
(`make web-assets` rebuilds everything under `web/static/`).
## Conventions ## Conventions
+10 -2
View File
@@ -1,4 +1,4 @@
.PHONY: build run test tidy lint docker hash-password nidusctl web-deps web-css templ-generate .PHONY: build run test tidy lint docker hash-password nidusctl web-deps web-css web-ts web-assets templ-generate
## build: compile the binary ## build: compile the binary
build: build:
@@ -43,7 +43,7 @@ nidusctl: build
templ-generate: templ-generate:
templ generate templ generate
## web-deps: install the Tailwind CLI (needed once, or after web/package.json changes) ## web-deps: install the Tailwind CLI + TypeScript compiler (needed once, or after web/package.json changes)
web-deps: web-deps:
cd web && npm install cd web && npm install
@@ -51,3 +51,11 @@ web-deps:
## Run this after editing any .templ file or web/input.css. ## Run this after editing any .templ file or web/input.css.
web-css: templ-generate web-css: templ-generate
cd web && npx @tailwindcss/cli -i ./input.css -o ./static/app.css --minify cd web && npx @tailwindcss/cli -i ./input.css -o ./static/app.css --minify
## web-ts: compile web/ts/*.ts to web/static/*.js
## Run this after editing any .ts file.
web-ts:
cd web && npx tsc -p tsconfig.json
## web-assets: rebuild everything under web/static/ (CSS + JS)
web-assets: web-css web-ts
+11 -8
View File
@@ -181,7 +181,8 @@ A small server-rendered dashboard is served at `/web/` (separate from the
DAV endpoints, which stay on HTTP Basic Auth): DAV endpoints, which stay on HTTP Basic Auth):
- **Login** (`/web/login`) — cookie-based session, stored server-side in - **Login** (`/web/login`) — cookie-based session, stored server-side in
`nidus.db` (`web_sessions` table), independent of DAV Basic Auth. `nidus.db` (`web_sessions` table), independent of DAV Basic Auth. Includes
a "Show/Hide" password toggle to rule out typos before submitting.
- **Dashboard** (`/web/`) — lists your own calendars/address books, who - **Dashboard** (`/web/`) — lists your own calendars/address books, who
they're shared with, and any resources other users have shared with you. they're shared with, and any resources other users have shared with you.
- **Share management** — add/remove shares directly from the dashboard - **Share management** — add/remove shares directly from the dashboard
@@ -190,16 +191,18 @@ DAV endpoints, which stay on HTTP Basic Auth):
- **Logout** (`/web/logout`). - **Logout** (`/web/logout`).
Implementation: [templ](https://templ.guide/) for type-safe Go HTML Implementation: [templ](https://templ.guide/) for type-safe Go HTML
templates, [Tailwind CSS v4](https://tailwindcss.com/) for styling, and templates, [Tailwind CSS v4](https://tailwindcss.com/) for styling, htmx
htmx for the sprinkles of dynamic behavior (form submission via for the sprinkles of dynamic behavior (form submission via POST/DELETE,
POST/DELETE, partial page swaps) — no separate JS build/framework needed. partial page swaps), and TypeScript (compiled to plain JS, `web/ts/`) for
The compiled CSS and the htmx bundle are embedded into the Go binary the few bits of client-side-only logic (e.g. the password-visibility
toggle) — no separate JS framework needed. The compiled CSS, compiled JS,
and the htmx bundle are all embedded into the Go binary
(`web/staticassets.go`), so no Node.js is required at runtime, only when (`web/staticassets.go`), so no Node.js is required at runtime, only when
you change styles or templates during development: you change styles, templates, or TypeScript during development:
```bash ```bash
make web-deps # once, installs the Tailwind CLI (needs Node.js/npm) make web-deps # once, installs the Tailwind CLI + TypeScript compiler (needs Node.js/npm)
make web-css # regenerate templ code + rebuild web/static/app.css make web-assets # regenerate templ code + rebuild web/static/app.css and web/static/*.js
``` ```
--- ---
+8 -1
View File
@@ -23,8 +23,14 @@ templ Login(errorMsg string) {
</div> </div>
<div> <div>
<label for="password" class="block text-sm font-medium text-gray-700">Password</label> <label for="password" class="block text-sm font-medium text-gray-700">Password</label>
<div class="mt-1 flex gap-2">
<input id="password" name="password" type="password" required <input id="password" name="password" type="password" required
class="mt-1 block w-full rounded-md border-gray-300 border px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"/> class="block w-full rounded-md border-gray-300 border px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"/>
<button type="button" id="toggle-password" aria-pressed="false"
class="shrink-0 rounded-md border border-gray-300 px-3 py-2 text-sm text-gray-600 hover:bg-gray-50">
Show
</button>
</div>
</div> </div>
<button type="submit" <button type="submit"
class="w-full bg-indigo-600 text-white rounded-md py-2 font-medium hover:bg-indigo-700"> class="w-full bg-indigo-600 text-white rounded-md py-2 font-medium hover:bg-indigo-700">
@@ -32,6 +38,7 @@ templ Login(errorMsg string) {
</button> </button>
</form> </form>
</div> </div>
<script type="module" src="/web/static/login.js"></script>
</body> </body>
</html> </html>
} }
+1 -1
View File
@@ -52,7 +52,7 @@ func Login(errorMsg string) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<form method=\"POST\" action=\"/web/login\" class=\"space-y-4\"><div><label for=\"username\" class=\"block text-sm font-medium text-gray-700\">Username</label> <input id=\"username\" name=\"username\" type=\"text\" required autofocus class=\"mt-1 block w-full rounded-md border-gray-300 border px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500\"></div><div><label for=\"password\" class=\"block text-sm font-medium text-gray-700\">Password</label> <input id=\"password\" name=\"password\" type=\"password\" required class=\"mt-1 block w-full rounded-md border-gray-300 border px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500\"></div><button type=\"submit\" class=\"w-full bg-indigo-600 text-white rounded-md py-2 font-medium hover:bg-indigo-700\">Sign in</button></form></div></body></html>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<form method=\"POST\" action=\"/web/login\" class=\"space-y-4\"><div><label for=\"username\" class=\"block text-sm font-medium text-gray-700\">Username</label> <input id=\"username\" name=\"username\" type=\"text\" required autofocus class=\"mt-1 block w-full rounded-md border-gray-300 border px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500\"></div><div><label for=\"password\" class=\"block text-sm font-medium text-gray-700\">Password</label><div class=\"mt-1 flex gap-2\"><input id=\"password\" name=\"password\" type=\"password\" required class=\"block w-full rounded-md border-gray-300 border px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500\"> <button type=\"button\" id=\"toggle-password\" aria-pressed=\"false\" class=\"shrink-0 rounded-md border border-gray-300 px-3 py-2 text-sm text-gray-600 hover:bg-gray-50\">Show</button></div></div><button type=\"submit\" class=\"w-full bg-indigo-600 text-white rounded-md py-2 font-medium hover:bg-indigo-700\">Sign in</button></form></div><script type=\"module\" src=\"/web/static/login.js\"></script></body></html>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
+377 -1
View File
@@ -7,7 +7,8 @@
"name": "nidus-web", "name": "nidus-web",
"devDependencies": { "devDependencies": {
"@tailwindcss/cli": "^4.3.3", "@tailwindcss/cli": "^4.3.3",
"tailwindcss": "^4.3.3" "tailwindcss": "^4.3.3",
"typescript": "^7.0.2"
} }
}, },
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
@@ -645,6 +646,346 @@
"node": ">= 20" "node": ">= 20"
} }
}, },
"node_modules/@typescript/typescript-aix-ppc64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
"integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-darwin-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
"integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-darwin-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
"integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-freebsd-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
"integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-freebsd-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
"integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-arm": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
"integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
"integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-loong64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
"integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
"cpu": [
"loong64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-mips64el": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
"integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-ppc64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
"integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-riscv64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
"integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-s390x": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
"integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
"cpu": [
"s390x"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
"integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-netbsd-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
"integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-netbsd-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
"integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-openbsd-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
"integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-openbsd-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
"integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-sunos-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
"integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-win32-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
"integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-win32-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
"integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
@@ -1123,6 +1464,41 @@
"engines": { "engines": {
"node": ">=8.0" "node": ">=8.0"
} }
},
"node_modules/typescript": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
"integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc"
},
"engines": {
"node": ">=16.20.0"
},
"optionalDependencies": {
"@typescript/typescript-aix-ppc64": "7.0.2",
"@typescript/typescript-darwin-arm64": "7.0.2",
"@typescript/typescript-darwin-x64": "7.0.2",
"@typescript/typescript-freebsd-arm64": "7.0.2",
"@typescript/typescript-freebsd-x64": "7.0.2",
"@typescript/typescript-linux-arm": "7.0.2",
"@typescript/typescript-linux-arm64": "7.0.2",
"@typescript/typescript-linux-loong64": "7.0.2",
"@typescript/typescript-linux-mips64el": "7.0.2",
"@typescript/typescript-linux-ppc64": "7.0.2",
"@typescript/typescript-linux-riscv64": "7.0.2",
"@typescript/typescript-linux-s390x": "7.0.2",
"@typescript/typescript-linux-x64": "7.0.2",
"@typescript/typescript-netbsd-arm64": "7.0.2",
"@typescript/typescript-netbsd-x64": "7.0.2",
"@typescript/typescript-openbsd-arm64": "7.0.2",
"@typescript/typescript-openbsd-x64": "7.0.2",
"@typescript/typescript-sunos-x64": "7.0.2",
"@typescript/typescript-win32-arm64": "7.0.2",
"@typescript/typescript-win32-x64": "7.0.2"
}
} }
} }
} }
+5 -2
View File
@@ -3,10 +3,13 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build:css": "tailwindcss -i ./input.css -o ./static/app.css --minify", "build:css": "tailwindcss -i ./input.css -o ./static/app.css --minify",
"watch:css": "tailwindcss -i ./input.css -o ./static/app.css --watch" "watch:css": "tailwindcss -i ./input.css -o ./static/app.css --watch",
"build:ts": "tsc -p tsconfig.json",
"watch:ts": "tsc -p tsconfig.json --watch"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/cli": "^4.3.3",
"tailwindcss": "^4.3.3", "tailwindcss": "^4.3.3",
"@tailwindcss/cli": "^4.3.3" "typescript": "^7.0.2"
} }
} }
+1 -1
View File
File diff suppressed because one or more lines are too long
+17
View File
@@ -0,0 +1,17 @@
"use strict";
// Login page behavior: adds a "show password" toggle so users can rule
// out typos before submitting. Compiled to web/static/login.js via
// `make web-js` (see web/tsconfig.json).
(function initPasswordToggle() {
const passwordInput = document.getElementById("password");
const toggleButton = document.getElementById("toggle-password");
if (!passwordInput || !toggleButton) {
return;
}
toggleButton.addEventListener("click", () => {
const showing = passwordInput.type === "text";
passwordInput.type = showing ? "password" : "text";
toggleButton.textContent = showing ? "Show" : "Hide";
toggleButton.setAttribute("aria-pressed", showing ? "false" : "true");
});
})();
+17
View File
@@ -0,0 +1,17 @@
// Login page behavior: adds a "show password" toggle so users can rule
// out typos before submitting. Compiled to web/static/login.js via
// `make web-js` (see web/tsconfig.json).
(function initPasswordToggle(): void {
const passwordInput = document.getElementById("password") as HTMLInputElement | null;
const toggleButton = document.getElementById("toggle-password") as HTMLButtonElement | null;
if (!passwordInput || !toggleButton) {
return;
}
toggleButton.addEventListener("click", () => {
const showing = passwordInput.type === "text";
passwordInput.type = showing ? "password" : "text";
toggleButton.textContent = showing ? "Show" : "Hide";
toggleButton.setAttribute("aria-pressed", showing ? "false" : "true");
});
})();
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "es2020",
"lib": ["ES2020", "DOM"],
"strict": true,
"noEmitOnError": true,
"rootDir": "./ts",
"outDir": "./static"
},
"include": ["ts/**/*.ts"]
}