feat(web): derive video-embed provider and id from a pasted URL #104

Merged
dominik.polakovics merged 1 commit from afk/97 into main 2026-07-18 03:40:16 +02:00

Closes #97

What

Web-editor-only convenience for the video-embed form: pasting a full YouTube or Vimeo URL into the Video ID field now derives the provider client-side — the provider select flips to the right value and the field reduces to the bare id. Anything that is not a recognized video URL is left exactly as typed. No sidecar, schema, or server changes; the stored content shape is identical, so nothing changes for agents, the server, or the shortcode.

How

  • web/src/videoEmbed.ts (new): pure parseVideoEmbedUrl(raw) helper. Parses via WHATWG URL (retrying once with an https:// prefix for scheme-less pastes), matches hosts exact-or-subdomain only, and extracts the id from youtube.com/watch?v=, youtu.be/, youtube.com/{shorts,embed,live}/, youtube-nocookie.com, vimeo.com/<id>, and player.vimeo.com/video/<id>. Query strings, fragments, and timestamps are dropped naturally; extracted ids are charset-validated (YouTube [A-Za-z0-9_-]{6,}, Vimeo digits). Bare ids fail the host match and return null.
  • web/src/components/FormPane.tsx: 7-line hook in ElementForm.update, guarded by schema.name === "video-embed" && name === "id". The raw write still happens first so the control re-syncs even when the same URL is pasted twice.
  • web/src/test/VideoEmbed.test.tsx (new): 21 Vitest cases — pure-parser coverage of every listed form plus passthrough/garbage/empty inputs, and mounted-form tests asserting field-id/field-provider behavior, the double-paste edge, and the /api/page/save payload.

Verification

  • npm test: 41 files, 813 tests passed (full suite, no regressions); tsc --noEmit clean.
  • go vet ./..., go test ./... (with hugo on PATH so nothing skips), go build — all green; frontend built with vite build.
  • Manual acceptance check driven in a real Chromium against harness serve: pasted youtube.com/watch?v=dQw4w9WgXcQ into the form → provider flipped to youtube, field reduced to the bare id; after save the markdown contains {{< video-embed id="dQw4w9WgXcQ" provider="youtube" … >}} and the rendered preview shows the click-to-load facade with data-video-provider="youtube" data-video-id="dQw4w9WgXcQ". Repeated with vimeo.com/76979871 (provider vimeo, numeric id, correct facade).

scaffold/site/AGENTS.md needs no update: the change alters no sidecar schema, field type, or canonicalization rule it documents.

🤖 Generated with Claude Code

Closes #97 ## What Web-editor-only convenience for the `video-embed` form: pasting a full YouTube or Vimeo URL into the **Video ID** field now derives the provider client-side — the `provider` select flips to the right value and the field reduces to the bare id. Anything that is not a recognized video URL is left exactly as typed. No sidecar, schema, or server changes; the stored content shape is identical, so nothing changes for agents, the server, or the shortcode. ## How - `web/src/videoEmbed.ts` (new): pure `parseVideoEmbedUrl(raw)` helper. Parses via WHATWG `URL` (retrying once with an `https://` prefix for scheme-less pastes), matches hosts exact-or-subdomain only, and extracts the id from `youtube.com/watch?v=`, `youtu.be/`, `youtube.com/{shorts,embed,live}/`, `youtube-nocookie.com`, `vimeo.com/<id>`, and `player.vimeo.com/video/<id>`. Query strings, fragments, and timestamps are dropped naturally; extracted ids are charset-validated (YouTube `[A-Za-z0-9_-]{6,}`, Vimeo digits). Bare ids fail the host match and return `null`. - `web/src/components/FormPane.tsx`: 7-line hook in `ElementForm.update`, guarded by `schema.name === "video-embed" && name === "id"`. The raw write still happens first so the control re-syncs even when the same URL is pasted twice. - `web/src/test/VideoEmbed.test.tsx` (new): 21 Vitest cases — pure-parser coverage of every listed form plus passthrough/garbage/empty inputs, and mounted-form tests asserting `field-id`/`field-provider` behavior, the double-paste edge, and the `/api/page/save` payload. ## Verification - `npm test`: 41 files, 813 tests passed (full suite, no regressions); `tsc --noEmit` clean. - `go vet ./...`, `go test ./...` (with hugo on PATH so nothing skips), `go build` — all green; frontend built with `vite build`. - Manual acceptance check driven in a real Chromium against `harness serve`: pasted `youtube.com/watch?v=dQw4w9WgXcQ` into the form → provider flipped to youtube, field reduced to the bare id; after save the markdown contains `{{< video-embed id="dQw4w9WgXcQ" provider="youtube" … >}}` and the rendered preview shows the click-to-load facade with `data-video-provider="youtube" data-video-id="dQw4w9WgXcQ"`. Repeated with `vimeo.com/76979871` (provider vimeo, numeric id, correct facade). `scaffold/site/AGENTS.md` needs no update: the change alters no sidecar schema, field type, or canonicalization rule it documents. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(web): derive video-embed provider and id from a pasted URL
All checks were successful
ci / build-and-test (pull_request) Successful in 9m27s
abe42f5b03
When an editor pastes a full YouTube or Vimeo URL into the video-embed
form's id field, the editor now derives the provider client-side, sets
the provider select, and reduces the field to the bare video id.
Recognized forms: youtube.com/watch?v=, youtu.be/, youtube.com/shorts|
embed|live/, youtube-nocookie.com, vimeo.com/<id>, and
player.vimeo.com/video/<id>, with query strings and timestamps
stripped; scheme-less pastes work too. Unrecognized input is left
untouched (assumed to already be a bare id). Storage format, sidecar,
and server are unchanged.

Closes #97

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Owner

This was generated by AI while landing a PR.

Validation verdict: PASS.

Verification signal relied on: the green Forgejo Actions run ci / build-and-test (success, 9m27s) — full Go and web suites, including the 21-case VideoEmbed.test.tsx. Not re-run.

Checked:

  • Merges cleanly into current main; no conflict.
  • Title is Conventional Commits (feat(web): …); body carries Closes #97 on afk/97.
  • Web-editor-only: touches only videoEmbed.ts (new pure parser), FormPane.tsx (+8-line hook), and tests. No sidecar/schema/server/shortcode change — stored content shape identical (acceptance criterion).
  • Parser: parseHttpUrl retries once with an https:// prefix for scheme-less pastes and rejects non-http(s); YouTube covers youtu.be, watch?v=, shorts/embed/live; Vimeo covers vimeo.com/<id> and player.vimeo.com/video/<id>. IDs are charset-validated; unrecognized input (incl. a bare id) returns null and is left untouched.
  • Domain matching is exact-or-subdomain (isDomain), never a substring test.
  • Form hook is guarded by schema.name === "video-embed" && name === "id", writes the raw value first so a repeated paste re-syncs, and only overwrites on a match.
  • scaffold/site/AGENTS.md correctly not updated: no documented sidecar/field/canonicalization surface changed.

No blockers. Awaiting a free-text merge go-ahead before merging.

> *This was generated by AI while landing a PR.* **Validation verdict: PASS.** Verification signal relied on: the green Forgejo Actions run `ci / build-and-test` (success, 9m27s) — full Go and web suites, including the 21-case `VideoEmbed.test.tsx`. Not re-run. Checked: - Merges cleanly into current `main`; no conflict. - Title is Conventional Commits (`feat(web): …`); body carries `Closes #97` on `afk/97`. - Web-editor-only: touches only `videoEmbed.ts` (new pure parser), `FormPane.tsx` (+8-line hook), and tests. No sidecar/schema/server/shortcode change — stored content shape identical (acceptance criterion). - Parser: `parseHttpUrl` retries once with an `https://` prefix for scheme-less pastes and rejects non-http(s); YouTube covers `youtu.be`, `watch?v=`, `shorts`/`embed`/`live`; Vimeo covers `vimeo.com/<id>` and `player.vimeo.com/video/<id>`. IDs are charset-validated; unrecognized input (incl. a bare id) returns null and is left untouched. - Domain matching is exact-or-subdomain (`isDomain`), never a substring test. - Form hook is guarded by `schema.name === "video-embed" && name === "id"`, writes the raw value first so a repeated paste re-syncs, and only overwrites on a match. - `scaffold/site/AGENTS.md` correctly not updated: no documented sidecar/field/canonicalization surface changed. No blockers. Awaiting a free-text merge go-ahead before merging.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/deckle!104
No description provided.