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:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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>
|
||||||
<input id="password" name="password" type="password" required
|
<div class="mt-1 flex gap-2">
|
||||||
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"/>
|
<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>
|
</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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+377
-1
@@ -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
@@ -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
File diff suppressed because one or more lines are too long
@@ -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");
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -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");
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "es2020",
|
||||||
|
"lib": ["ES2020", "DOM"],
|
||||||
|
"strict": true,
|
||||||
|
"noEmitOnError": true,
|
||||||
|
"rootDir": "./ts",
|
||||||
|
"outDir": "./static"
|
||||||
|
},
|
||||||
|
"include": ["ts/**/*.ts"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user