etherpad-lite/admin
John McLear 189ea4a29b
fix: URL-encode pad names in admin 'Open' button and recent pads (#7865) (#7895)
* fix: URL-encode pad names in admin 'Open' button and recent pads (#7865)

- encodeURIComponent in admin PadPage 'Open' button href
- decodeURIComponent when reading pad name from URL pathname
  in pad_userlist.ts and colibris/pad.js (recent pads storage)
- encodeURIComponent in colibris/index.js recent pads href;
  display text uses stored name directly (no double-decode)
- add recent_pads spec asserting encoded URLs
- add share dialog spec asserting URL encoding of special chars

* fix: address Qodo review on recent-pads encoding and admin Open button

- Normalize legacy URL-encoded recentPads names before re-encoding the
  href in colibris/index.js, preventing double-encoding (%2F -> %252F)
  of entries stored by older versions.
- Add noopener,noreferrer to the admin 'Open' window.open call to
  prevent reverse tabnabbing, matching the pattern used elsewhere.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:02:35 +01:00
..
public fix(a11y): add Dialog titles/descriptions and missing index.code key (#7836) 2026-05-25 10:43:10 +01:00
scripts feat(admin): document admin endpoints in OpenAPI (#7693) (#7705) 2026-05-10 15:55:33 +08:00
src fix: URL-encode pad names in admin 'Open' button and recent pads (#7865) (#7895) 2026-06-05 14:02:35 +01:00
.eslintrc.cjs Feat/admin react (#6211) 2024-03-09 23:07:09 +01:00
.gitignore Feat/admin react (#6211) 2024-03-09 23:07:09 +01:00
index.html Feat/admin react (#6211) 2024-03-09 23:07:09 +01:00
package.json build(deps): bump @tanstack/react-query from 5.100.14 to 5.101.0 (#7889) 2026-06-05 12:30:02 +01:00
README.md fix(admin/pads): apply filter chip server-side, before pagination (#7798) 2026-05-17 19:43:36 +01:00
tsconfig.json feat(admin): document admin endpoints in OpenAPI (#7693) (#7705) 2026-05-10 15:55:33 +08:00
tsconfig.node.json Feat/admin react (#6211) 2024-03-09 23:07:09 +01:00
vite.config.ts admin: parsed JSONC settings editor (takes over #7666, closes #7603) (#7709) 2026-05-15 10:41:37 +01:00

Admin UI

Vite + React 19 single-page app served at /admin. Talks to the backend over socket.io for the existing settings / plugins / pads pages, and (when endpoints are added to the OpenAPI spec) over a typed REST client.

Scripts

Script What it does
pnpm dev gen:api + Vite dev server (expects backend on :9001).
pnpm gen:api Regenerates src/api/{schema.d.ts,version.ts} from the OpenAPI spec.
pnpm build gen:api + tsc + vite build.
pnpm build-copy Same, but writes into ../src/templates/admin.
pnpm test gen:api + smoke tests for the API client wiring.
pnpm lint ESLint.

Typed API client

The admin uses openapi-typescript to generate types from src/node/hooks/express/openapi.ts, openapi-fetch for typed requests, and openapi-react-query for TanStack Query bindings.

Generated files

admin/src/api/schema.d.ts and admin/src/api/version.ts are generated by gen:api and gitignored — never commit them. They are produced by:

pnpm --filter admin gen:api

admin/scripts/gen-api.mjs loads src/node/hooks/express/openapi.ts, calls generateDefinitionForVersion for the latest API version, pipes the JSON through openapi-typescript to produce schema.d.ts, and emits a runtime constant LATEST_API_VERSION (read from info.version in the spec) to version.ts so client.ts can build the right /api/<version>/ baseUrl.

gen:api runs as the first step of dev, build, build-copy, and test, so a fresh checkout produces the generated files automatically when any of those scripts is invoked. After modifying any of the following, the next pnpm <dev|build|test> will refresh the generated files; you can also run gen:api directly:

  • src/node/hooks/express/openapi.ts
  • src/node/handler/APIHandler.ts (changes to latestApiVersion)
  • the resource definitions referenced by openapi.ts

Using the client

import { $api } from './api/client';

const SettingsPanel = () => {
  const { data } = $api.useQuery('get', '/admin/settings'); // example
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
};

The admin endpoints are not yet present in the OpenAPI spec — this client is in place to support upcoming work (see issue #7638 follow-up). For now, it is exercised only by the smoke test.

Socket.io: padLoad query shape

The admin /settings namespace's padLoad event accepts a PadSearchQuery defined in src/node/types/PadSearchQuery.ts:

field type required notes
pattern string yes Substring match on pad name.
offset number yes Pagination start, in items. Clamped server-side.
limit number yes Page size. Capped at 12.
ascending boolean yes Sort direction.
sortBy "padName" | "lastEdited" | "userCount" | "revisionNumber" yes Column to sort by.
filter "all" | "active" | "recent" | "empty" | "stale" (opt.) no Filter chip; defaults to "all". Applied before pagination so total and the page slice both reflect the filtered universe. Older clients that omit the field get the unchanged "all" behaviour.

Filter semantics — applied after pattern matching, before sort + slice:

  • active: userCount > 0
  • recent: edited within the last 7 days
  • empty: revisionNumber === 0
  • stale: not edited in the last 365 days
  • all / missing: no further filtering